Repository: oriontvv/pyaspeller (language: python)

Issue:
Use pyproject.toml
Migrate to pyproject.toml


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 3d0af83..1ff9b14 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -3,11 +3,7 @@
 
 name: ci
 
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
+on: [push]
 
 jobs:
   build:
@@ -30,22 +26,13 @@ jobs:
         python-version: ${{ matrix.python-version }}
     - name: Install dependencies
       run: |
-        python -m pip install --upgrade pip setuptools wheel
-        python -m pip install -r requirements-dev.txt
-        python setup.py develop
-    - name: Lint with flake8
+        make init
+    - name: Linters
       run: |
-        # stop the build if there are Python syntax errors or undefined names
-        python -m flake8 pyaspeller tests examples --count --select=E9,F63,F7,F82 --show-source --statistics
-        # exit-zero treats all errors as warnings
-        python -m flake8 pyaspeller tests examples --count --exit-zero --max-complexity=10 --max-line-length=80 --statistics
-    - name: Test with pytest and coverage
+        make lint
+    - name: Test and coverage
       run: |
-        python -m pytest tests --cov=pyaspeller
-        python -m coverage report -m
-    - name: Check setup
-      run: |
-        python setup.py check -rm
+        make coverage-report
     - name: Coveralls
       uses: AndreMiras/coveralls-python-action@develop
       with:
diff --git a/.gitignore b/.gitignore
index 5400242..bdd13d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,6 +65,7 @@ py3/
 py2/
 venv
 env
+.venv
 
 .idea
 .vscode
\ No newline at end of file
diff --git a/HISTORY.rst b/HISTORY.md
similarity index 76%
rename from HISTORY.rst
rename to HISTORY.md
index 0d01413..052c0f2 100644
--- a/HISTORY.rst
+++ b/HISTORY.md
@@ -1,6 +1,9 @@
+# 0.X.X (2022-XX-XX)
+* migrate to pyproject.toml
+* add python3.10 support
 
-0.2.0 (2020-12-26)
-------------------
+
+# 0.2.0 (2020-12-26)
 * added methods spelled, spell_path to Speller class
 * use default format `plain`
 * rewrite unittests to pytest
@@ -8,8 +11,7 @@
 * updated example
 
 
-0.1.0 (2016-03-03)
-------------------
+# 0.1.0 (2016-03-03)
 * add python2.7 support
 * add Word quick spell class
 * testing and coverage with pytest
@@ -19,33 +21,26 @@
 * add restrictions link in readme
 * add tests, increase coverage
 
-0.0.5 (2015-10-04)
-------------------
 
+# 0.0.5 (2015-10-04)
 * wheel packaging
 
 
-0.0.4 (2015-10-08)
-------------------
-
+# 0.0.4 (2015-10-08)
 * system script *pyaspeller* added
 * packet classifiers updated
 * alpha status specified
 * contributors files added
 
 
-0.0.3 (2015-09-31)
-------------------
-
+# 0.0.3 (2015-09-31)
 * uploaded to pypi.
 * test coverage increased.
 * Pretty YandexSpeller API
 * Spell checking of strings with parameters.
 
 
-0.0.2 (2015-09-23)
-------------------
-
+# 0.0.2 (2015-09-23)
 * testing automated
 * coverage setup
 * basic functionality. spell checking string
diff --git a/Makefile b/Makefile
index 77607ac..351e9ea 100644
--- a/Makefile
+++ b/Makefile
@@ -1,30 +1,64 @@
-# Some simple testing tasks (sorry, UNIX only).
+CODE =\
+	src \
+	examples
 
-FLAGS=
+TESTS =\
+	tests
 
+ALL = $(CODE)\
+	$(TESTS)
 
-flake:
-#	python setup.py check -rms
-	flake8 pyaspeller tests examples
+VENV ?= .venv
+JOBS ?= 4
 
-develop:
-	python setup.py develop
 
-test: develop
-	py.test tests --ignore=.DS_Store
+init:
+	python3 -m venv $(VENV)
+	$(VENV)/bin/python -m pip install -U pip
+	$(VENV)/bin/python -m pip install poetry
+	$(VENV)/bin/poetry install
 
-vtest: develop
-	py.test tests -v --ignore=.DS_Store
+lint: black-lint
+# flake8 mypy pytest-lint
 
+black-lint:
+	$(VENV)/bin/black --check $(CODE)
 
-cov cover coverage: flake
-	py.test tests --cov=pyaspeller --ignore=.DS_Store
+black-format:
+	$(VENV)/bin/black $(CODE)
 
-package: cov
-	python setup.py sdist bdist_wheel
+flake8:
+	$(VENV)/bin/flake8 --statistics --jobs $(JOBS) --show-source $(ALL)
 
-publish: package
-	python setup.py sdist bdist_wheel upload
+mypy:
+	$(VENV)/bin/mypy --install-types --non-interactive $(CODE)
+
+pytest-lint:
+	$(VENV)/bin/pytest --dead-fixtures --dup-fixtures
+
+pretty: black-format \
+	$(VENV)/bin/isort $(ALL)
+
+plint: pretty lint
+
+precommit_install:
+	@git init
+	echo '#!/bin/sh\nmake lint\n' >> .git/hooks/pre-commit
+	chmod +x .git/hooks/pre-commit
+
+#develop:
+#	python setup.py develop
+
+
+coverage-report:
+	$(VENV)/bin/pytest tests --cov=src --ignore=.DS_Store
+	$(VENV)/bin/coverage report -m
+
+#package: cov
+#	python setup.py sdist bdist_wheel
+
+#publish: package
+#	python setup.py sdist bdist_wheel upload
 
 clean:
 	rm -rf `find . -name __pycache__`
@@ -39,19 +73,13 @@ clean:
 	rm -rf coverage
 	rm -rf build
 	rm -rf cover
-	make -C docs clean
-	python setup.py clean
-	rm -rf .tox
+
+env:
+	source $(VENV)/bin/activate
 
 doc:
 	make -C docs html
 	@echo "open file://`pwd`/docs/_build/html/index.html"
 
 doc-spelling:
-	make -C docs spelling
-
-env:
-	$(source ./py3/bin/activate)
-
-
-.PHONY: all build env flake test vtest cov clean doc
+	make -C docs spelling
\ No newline at end of file
diff --git a/examples/example.py b/examples/example.py
index 22a1cde..d7d003b 100644
--- a/examples/example.py
+++ b/examples/example.py
@@ -2,10 +2,9 @@
 
 from pyaspeller import YandexSpeller
 
-
-speller = YandexSpeller(lang='en', ignore_digits=True)
+speller = YandexSpeller(lang="en", ignore_digits=True)
 spelled = speller.spelled("42 is a cUl maagic namber")
-assert spelled == '42 is a cool magic number'
+assert spelled == "42 is a cool magic number"
 
 
 changes = speller.spell("42 is a cUl maagic namber")
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..e3b6c6c
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,673 @@
+[[package]]
+name = "atomicwrites"
+version = "1.4.0"
+description = "Atomic file writes."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "attrs"
+version = "21.4.0"
+description = "Classes Without Boilerplate"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
+
+[[package]]
+name = "black"
+version = "22.1.0"
+description = "The uncompromising code formatter."
+category = "dev"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+click = ">=8.0.0"
+dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
+mypy-extensions = ">=0.4.3"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = ">=1.1.0"
+typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
+typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "certifi"
+version = "2021.10.8"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.0.11"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.5.0"
+
+[package.extras]
+unicode_backport = ["unicodedata2"]
+
+[[package]]
+name = "click"
+version = "8.0.3"
+description = "Composable command line interface toolkit"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.4"
+description = "Cross-platform colored terminal text."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "coverage"
+version = "6.2"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
+
+[package.extras]
+toml = ["tomli"]
+
+[[package]]
+name = "coveralls"
+version = "3.3.1"
+description = "Show coverage stats online via coveralls.io"
+category = "dev"
+optional = false
+python-versions = ">= 3.5"
+
+[package.dependencies]
+coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0"
+docopt = ">=0.6.1"
+requests = ">=1.0.0"
+
+[package.extras]
+yaml = ["PyYAML (>=3.10)"]
+
+[[package]]
+name = "dataclasses"
+version = "0.8"
+description = "A backport of the dataclasses module for Python 3.6"
+category = "dev"
+optional = false
+python-versions = ">=3.6, <3.7"
+
+[[package]]
+name = "docopt"
+version = "0.6.2"
+description = "Pythonic argument parser, that will make you smile"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "flake8"
+version = "4.0.1"
+description = "the modular source code checker: pep8 pyflakes and co"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""}
+mccabe = ">=0.6.0,<0.7.0"
+pycodestyle = ">=2.8.0,<2.9.0"
+pyflakes = ">=2.4.0,<2.5.0"
+
+[[package]]
+name = "idna"
+version = "3.3"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "importlib-metadata"
+version = "4.2.0"
+description = "Read metadata from Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
+
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "isort"
+version = "5.10.1"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1,<4.0"
+
+[package.extras]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+plugins = ["setuptools"]
+
+[[package]]
+name = "mccabe"
+version = "0.6.1"
+description = "McCabe checker, plugin for flake8"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "mypy"
+version = "0.931"
+description = "Optional static typing for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3"
+tomli = ">=1.1.0"
+typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
+typing-extensions = ">=3.10"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+python2 = ["typed-ast (>=1.4.0,<2)"]
+
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "packaging"
+version = "21.3"
+description = "Core utilities for Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+
+[[package]]
+name = "pathspec"
+version = "0.9.0"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[[package]]
+name = "platformdirs"
+version = "2.4.0"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
+test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+
+[[package]]
+name = "pluggy"
+version = "1.0.0"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "py"
+version = "1.11.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pycodestyle"
+version = "2.8.0"
+description = "Python style guide checker"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pyflakes"
+version = "2.4.0"
+description = "passive checker of Python programs"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pyparsing"
+version = "3.0.7"
+description = "Python parsing module"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "pytest"
+version = "6.2.5"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+py = ">=1.8.2"
+toml = "*"
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
+[[package]]
+name = "pytest-cov"
+version = "3.0.0"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
+
+[[package]]
+name = "requests"
+version = "2.27.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
+idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "tomli"
+version = "1.2.3"
+description = "A lil' TOML parser"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "typed-ast"
+version = "1.5.2"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "typing-extensions"
+version = "4.0.1"
+description = "Backported and Experimental Type Hints for Python 3.6+"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "urllib3"
+version = "1.26.8"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.extras]
+brotli = ["brotlipy (>=0.6.0)"]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[[package]]
+name = "zipp"
+version = "3.6.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.6.2"
+content-hash = "67d713a0a1a48a2fe9f65af1181175c832e958728a9c6983618dc0ad8a091db3"
+
+[metadata.files]
+atomicwrites = [
+    {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
+    {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
+]
+attrs = [
+    {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
+    {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
+]
+black = [
+    {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
+    {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
+    {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
+    {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
+    {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
+    {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
+    {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
+    {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
+    {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
+    {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
+    {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
+    {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
+    {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
+    {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
+    {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
+    {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
+    {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
+    {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
+    {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
+    {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
+    {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
+    {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
+    {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
+]
+certifi = [
+    {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
+    {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
+]
+charset-normalizer = [
+    {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"},
+    {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"},
+]
+click = [
+    {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
+    {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
+]
+colorama = [
+    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
+]
+coverage = [
+    {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"},
+    {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"},
+    {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"},
+    {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"},
+    {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"},
+    {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"},
+    {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"},
+    {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"},
+    {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"},
+    {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"},
+    {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"},
+    {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"},
+    {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"},
+    {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"},
+    {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"},
+    {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"},
+    {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"},
+    {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"},
+    {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"},
+    {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"},
+]
+coveralls = [
+    {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"},
+    {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"},
+]
+dataclasses = [
+    {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
+    {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
+]
+docopt = [
+    {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
+]
+flake8 = [
+    {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
+    {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
+]
+idna = [
+    {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+    {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+]
+importlib-metadata = [
+    {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"},
+    {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"},
+]
+iniconfig = [
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
+isort = [
+    {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
+    {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
+]
+mccabe = [
+    {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+    {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
+mypy = [
+    {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"},
+    {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"},
+    {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"},
+    {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"},
+    {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"},
+    {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"},
+    {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"},
+    {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"},
+    {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"},
+    {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"},
+    {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"},
+    {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"},
+    {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"},
+    {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"},
+    {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"},
+    {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"},
+    {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"},
+    {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"},
+    {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"},
+    {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"},
+]
+mypy-extensions = [
+    {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+    {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+packaging = [
+    {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+    {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+]
+pathspec = [
+    {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
+    {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
+]
+platformdirs = [
+    {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
+    {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
+]
+pluggy = [
+    {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
+    {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
+]
+py = [
+    {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+    {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
+]
+pycodestyle = [
+    {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
+    {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
+]
+pyflakes = [
+    {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
+    {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
+]
+pyparsing = [
+    {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
+    {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
+]
+pytest = [
+    {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
+    {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
+]
+pytest-cov = [
+    {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
+    {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
+]
+requests = [
+    {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
+    {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
+]
+toml = [
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+tomli = [
+    {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
+    {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
+]
+typed-ast = [
+    {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"},
+    {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"},
+    {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"},
+    {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"},
+    {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"},
+    {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"},
+    {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"},
+    {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"},
+    {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"},
+    {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"},
+    {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"},
+    {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"},
+    {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"},
+    {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"},
+    {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"},
+    {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"},
+    {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"},
+    {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"},
+    {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"},
+    {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"},
+    {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"},
+    {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"},
+    {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"},
+    {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"},
+]
+typing-extensions = [
+    {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"},
+    {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"},
+]
+urllib3 = [
+    {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
+    {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
+]
+zipp = [
+    {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
+    {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
+]
diff --git a/pyaspeller/__init__.py b/pyaspeller/__init__.py
deleted file mode 100644
index fc35ede..0000000
--- a/pyaspeller/__init__.py
+++ /dev/null
@@ -1,128 +0,0 @@
-"""
-Init module of pyaspeller package
-"""
-
-import logging
-from argparse import ArgumentParser
-
-from .speller import Speller  # noqa
-from .yandex_speller import YandexSpeller  # noqa
-from .word import Word  # noqa
-
-__version__ = '0.3.0'
-__all__ = ['main']
-
-
-def _create_args_parser():
-    description = "Search tool typos in the text, files and websites."
-    parser = ArgumentParser(description=description, prog='pyaspeller')
-    parser.add_argument('-v', '--version', action='version',
-                        version='%(prog)s ' + __version__)
-
-    parser.add_argument('text_or_path_or_url', help="text or path or url")
-
-    parser.add_argument('--config_path', default=None,
-                        help="Configuration file path")
-
-    parser.add_argument('--debug', action="store_const",
-                        help="print debug information",
-                        dest="log_level", const=logging.DEBUG,
-                        default=logging.WARNING)
-
-    parser.add_argument('--verbose', action="store_const",
-                        help="increase verbosity level",
-                        dest="log_level", const=logging.INFO,
-                        default=logging.WARNING)
-
-    parser.add_argument('-f', '--format', default='auto',
-                        choices=('plain', 'html', 'markdown', 'auto'),
-                        help="formats")
-
-    parser.add_argument('-l', '--lang', default=['en', 'ru'], nargs='+',
-                        choices=('en', 'ru', 'uk', 'kk'), help="languages")
-
-    parser.add_argument('-c', '--config', default=None,
-                        help="config path")
-
-    parser.add_argument('-d', '--dictionary', default=None,
-                        help="path to custom json dictionary file")
-
-    parser.add_argument('-r', '--report_type', default='console',
-                        choices=('console', 'html', 'markdown', 'json'),
-                        help="type of report")
-
-    parser.add_argument('--check_yo', default=True, action='store_true',
-                        help="check the correctness of using the "
-                             "letter “Ё” (Yo) in Russian texts")
-
-    parser.add_argument('--find-repeat-words', action='store_true',
-                        help="highlight repetitions of words, consecutive. "
-                             "For example, I flew to to to Cyprus")
-
-    parser.add_argument('--flag-latin', action='store_true',
-                        help="celebrate words, written in Latin, as erroneous")
-
-    parser.add_argument('--ignore-tags', action='store_true',
-                        help="ignore HTML tags. "
-                             "Default: code,kbd,object,samp,script,style,var "
-                             "Option to formats html and markdown")
-
-    parser.add_argument('--ignore_capitalization', action='store_true',
-                        help="ignore the incorrect use of UPPERCASE/lowercase "
-                             "letters, for example, in the word moscow")
-
-    parser.add_argument('--ignore_digits', action='store_true',
-                        help="ignore words with numbers, such as avp17h4534")
-
-    parser.add_argument('--ignore-latin', action='store_true',
-                        help="ignore words, written in Latin, like 'madrid'")
-
-    parser.add_argument('--ignore-roman-numerals', action='store_true',
-                        help="ignore Roman numerals I, II, III, ...")
-
-    parser.add_argument('--ignore_uppercase', action='store_true',
-                        help="ignore words written in capital letters")
-
-    parser.add_argument('--ignore_urls', action='store_true',
-                        help="ignore Internet addresses, email "
-                             "addresses and filenames")
-
-    parser.add_argument('--max-requests', default=2,
-                        help="Max count of requests at a time")
-
-    return parser
-
-
-def _create_speller(args):
-    speller = YandexSpeller(format_text=args.format,
-                            lang=args.lang,
-                            config_path=args.config_path,
-                            dictionary=args.dictionary,
-                            report_type=args.report_type,
-                            is_debug=args.log_level == logging.DEBUG,
-                            check_yo=args.check_yo,
-                            ignore_urls=args.ignore_urls,
-                            ignore_tags=args.ignore_tags,
-                            ignore_capitalization=args.ignore_capitalization,
-                            ignore_digits=args.ignore_digits,
-                            ignore_latin=args.ignore_latin,
-                            ignore_roman_numerals=args.ignore_roman_numerals,
-                            ignore_uppercase=args.ignore_uppercase,
-                            find_repeat_words=args.find_repeat_words,
-                            flag_latin=args.flag_latin)
-
-    return speller
-
-
-def main():
-    """
-    Main function. Uses as cli launcher
-    """
-    args = _create_args_parser().parse_args()
-    logging.basicConfig(level=args.log_level)
-
-    speller = _create_speller(args)
-
-    result = speller.spelled(args.text_or_path_or_url)
-    if result:
-        print(result)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..d1ac9ca
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,93 @@
+[tool.poetry]
+name = "pyaspeller"
+version = "0.2.0"
+description = "Search tool typos in the text, files and websites."
+authors = [
+    "Vassiliy Taranov <taranov.vv@gmail.com>",
+]
+license = "Apache 2"
+readme = "README.md"
+homepage = "https://github.com/oriontvv/pyaspeller"
+repository = "https://github.com/oriontvv/pyaspeller"
+documentation = "https://github.com/oriontvv/pyaspeller"
+keywords = [
+    "spell",
+    "spelling",
+    "spellcheck",
+    "api",
+    "yandex",
+    "text",
+]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "License :: OSI Approved :: Apache Software License",
+    "Operating System :: OS Independent",
+    "Intended Audience :: Developers",
+    "Intended Audience :: End Users/Desktop",
+    "Programming Language :: Python :: 3",
+    "Topic :: Text Processing :: Linguistic"
+]
+packages = [
+    { include = "pyaspeller", from = "src" }
+]
+
+[tool.poetry.scripts]
+pyaspeller = "src.pyaspeller.__init__:main"
+
+[tool.poetry.dependencies]
+python = "^3.6.2"
+requests = "2.27.1"
+
+[tool.poetry.dev-dependencies]
+coveralls = "3.3.1"
+pytest = "6.2.5"
+pytest-cov = "3.0.0"
+flake8 = "4.0.1"
+black = "22.1.0"
+mypy = "0.931"
+isort = "5.10.1"
+
+
+[build-system]
+requires = [
+    "poetry>=0.12",
+]
+build-backend = "poetry.masonry.api"
+
+[tool.flake8]
+ignore = [
+    "N801",
+    "N802",
+    "N803",
+    "E226",
+]
+max-complexity = 10
+
+[tool.pytest]
+addopts = "--ignore=py3 --ignore=build"
+
+
+[tool.black]
+line-length = 120
+exclude = '''
+/(
+    \.git
+  | \.hg
+  | \.mypy_cache
+  | \.tox
+  | \.venv
+  | _build
+  | buck-out
+  | build
+  | dist
+  | migrations
+)/
+'''
+
+[tool.isort]
+# make it compatible with black
+profile = "black"
+# Make sure this matches `*.py` in .editorconfig
+ensure_newline_before_comments = true
+include_trailing_comma = true
+use_parentheses = true
diff --git a/requirements-dev.txt b/requirements-dev.txt
deleted file mode 100644
index 05acd82..0000000
--- a/requirements-dev.txt
+++ /dev/null
@@ -1,5 +0,0 @@
--r requirements.txt
-coveralls==3.3.1
-pytest==6.2.5
-pytest-cov==3.0.0
-flake8==4.0.1
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index a743bbe..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-requests==2.27.1
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 6f117e5..0000000
--- a/setup.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import codecs
-import os
-import re
-from setuptools import setup, find_packages
-
-BASE_PATH = os.path.abspath(os.path.dirname(__file__))
-
-with codecs.open(os.path.join(BASE_PATH, 'pyaspeller', '__init__.py'), 'r',
-                 'latin1') as fp:
-    try:
-        version = re.findall(r"^__version__ = '([^']+)'\r?$",
-                             fp.read(), re.M)[0]
-    except IndexError:
-        raise RuntimeError('Unable to determine version.')
-
-
-def read(f):
-    return open(os.path.join(BASE_PATH, f)).read().strip()
-
-
-install_requires = ['requests']
-
-
-class BuildFailed(Exception):
-    pass
-
-
-setup(
-    name='pyaspeller',
-    version=version,
-    description="Search tool typos in the text, files and websites.",
-    long_description=read('README.md'),
-    long_description_content_type="text/markdown",
-    classifiers=[
-        'Development Status :: 4 - Beta',
-        'License :: OSI Approved :: Apache Software License',
-        'Intended Audience :: Developers',
-        'Intended Audience :: End Users/Desktop',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3 :: Only',
-        'Programming Language :: Python :: 3.6',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Topic :: Text Processing :: Linguistic'],
-    python_requires='>=3.6',
-    author='Vassiliy Taranov',
-    author_email='taranov.vv@gmail.com',
-    url='https://github.com/oriontvv/pyaspeller',
-    license='Apache 2',
-    packages=find_packages(),
-    install_requires=install_requires,
-    entry_points={
-        'console_scripts': [
-            'pyaspeller = pyaspeller.__init__:main'
-        ]
-    },
-    test_suite='tests',
-    include_package_data=True,
-)
diff --git a/src/pyaspeller/__init__.py b/src/pyaspeller/__init__.py
new file mode 100644
index 0000000..c4542b9
--- /dev/null
+++ b/src/pyaspeller/__init__.py
@@ -0,0 +1,137 @@
+"""
+Init module of pyaspeller package
+"""
+
+import logging
+from argparse import ArgumentParser
+
+from .speller import Speller  # noqa
+from .word import Word  # noqa
+from .yandex_speller import YandexSpeller  # noqa
+
+__version__ = "0.3.0"
+__all__ = ["main"]
+
+
+def _create_args_parser():
+    description = "Search tool typos in the text, files and websites."
+    parser = ArgumentParser(description=description, prog="pyaspeller")
+    parser.add_argument("-v", "--version", action="version", version="%(prog)s " + __version__)
+
+    parser.add_argument("text_or_path_or_url", help="text or path or url")
+
+    parser.add_argument("--config_path", default=None, help="Configuration file path")
+
+    parser.add_argument(
+        "--debug",
+        action="store_const",
+        help="print debug information",
+        dest="log_level",
+        const=logging.DEBUG,
+        default=logging.WARNING,
+    )
+
+    parser.add_argument(
+        "--verbose",
+        action="store_const",
+        help="increase verbosity level",
+        dest="log_level",
+        const=logging.INFO,
+        default=logging.WARNING,
+    )
+
+    parser.add_argument("-f", "--format", default="auto", choices=("plain", "html", "markdown", "auto"), help="formats")
+
+    parser.add_argument(
+        "-l", "--lang", default=["en", "ru"], nargs="+", choices=("en", "ru", "uk", "kk"), help="languages"
+    )
+
+    parser.add_argument("-c", "--config", default=None, help="config path")
+
+    parser.add_argument("-d", "--dictionary", default=None, help="path to custom json dictionary file")
+
+    parser.add_argument(
+        "-r", "--report_type", default="console", choices=("console", "html", "markdown", "json"), help="type of report"
+    )
+
+    parser.add_argument(
+        "--check_yo",
+        default=True,
+        action="store_true",
+        help="check the correctness of using the " "letter “Ё” (Yo) in Russian texts",
+    )
+
+    parser.add_argument(
+        "--find-repeat-words",
+        action="store_true",
+        help="highlight repetitions of words, consecutive. " "For example, I flew to to to Cyprus",
+    )
+
+    parser.add_argument("--flag-latin", action="store_true", help="celebrate words, written in Latin, as erroneous")
+
+    parser.add_argument(
+        "--ignore-tags",
+        action="store_true",
+        help="ignore HTML tags. "
+        "Default: code,kbd,object,samp,script,style,var "
+        "Option to formats html and markdown",
+    )
+
+    parser.add_argument(
+        "--ignore_capitalization",
+        action="store_true",
+        help="ignore the incorrect use of UPPERCASE/lowercase " "letters, for example, in the word moscow",
+    )
+
+    parser.add_argument("--ignore_digits", action="store_true", help="ignore words with numbers, such as avp17h4534")
+
+    parser.add_argument("--ignore-latin", action="store_true", help="ignore words, written in Latin, like 'madrid'")
+
+    parser.add_argument("--ignore-roman-numerals", action="store_true", help="ignore Roman numerals I, II, III, ...")
+
+    parser.add_argument("--ignore_uppercase", action="store_true", help="ignore words written in capital letters")
+
+    parser.add_argument(
+        "--ignore_urls", action="store_true", help="ignore Internet addresses, email " "addresses and filenames"
+    )
+
+    parser.add_argument("--max-requests", default=2, help="Max count of requests at a time")
+
+    return parser
+
+
+def _create_speller(args):
+    speller = YandexSpeller(
+        format_text=args.format,
+        lang=args.lang,
+        config_path=args.config_path,
+        dictionary=args.dictionary,
+        report_type=args.report_type,
+        is_debug=args.log_level == logging.DEBUG,
+        check_yo=args.check_yo,
+        ignore_urls=args.ignore_urls,
+        ignore_tags=args.ignore_tags,
+        ignore_capitalization=args.ignore_capitalization,
+        ignore_digits=args.ignore_digits,
+        ignore_latin=args.ignore_latin,
+        ignore_roman_numerals=args.ignore_roman_numerals,
+        ignore_uppercase=args.ignore_uppercase,
+        find_repeat_words=args.find_repeat_words,
+        flag_latin=args.flag_latin,
+    )
+
+    return speller
+
+
+def main():
+    """
+    Main function. Uses as cli launcher
+    """
+    args = _create_args_parser().parse_args()
+    logging.basicConfig(level=args.log_level)
+
+    speller = _create_speller(args)
+
+    result = speller.spelled(args.text_or_path_or_url)
+    if result:
+        print(result)
diff --git a/pyaspeller/errors.py b/src/pyaspeller/errors.py
similarity index 70%
rename from pyaspeller/errors.py
rename to src/pyaspeller/errors.py
index ad52d2c..f28a8c0 100644
--- a/pyaspeller/errors.py
+++ b/src/pyaspeller/errors.py
@@ -5,11 +5,11 @@
 
 class BaseSpellerError(Exception):
     """
-        Basic error wrapper
+    Basic error wrapper
     """
 
 
 class BadArgumentError(BaseSpellerError):
     """
-        Invalid argument was given
+    Invalid argument was given
     """
diff --git a/pyaspeller/speller.py b/src/pyaspeller/speller.py
similarity index 90%
rename from pyaspeller/speller.py
rename to src/pyaspeller/speller.py
index b9d13f3..54b4cb6 100644
--- a/pyaspeller/speller.py
+++ b/src/pyaspeller/speller.py
@@ -2,19 +2,18 @@
 Contains definitions of spellers.
 """
 
-import os
 import logging
+import os
 from typing import Iterable
 
 import requests
 
 from .errors import BadArgumentError
 
-
 _subs = {
-    '\r\n': '\n',  # Fix Windows
-    '\r': '\n',  # Fix MacOS
-    '\n+': '\n',  # Repeat line ends
+    "\r\n": "\n",  # Fix Windows
+    "\r": "\n",  # Fix MacOS
+    "\n+": "\n",  # Repeat line ends
 }
 
 
@@ -30,7 +29,7 @@ def _fetch_content(text: str) -> str:
 
 
 def is_url(text: str) -> bool:
-    return text.startswith(('http://', 'https://'))
+    return text.startswith(("http://", "https://"))
 
 
 def is_path(path: str) -> bool:
@@ -53,10 +52,10 @@ def spell(self, text: str) -> Iterable[object]:
         {'code': 1, 'pos': 19, 'row': 0, 'col': 19, 'len': 6,
             'word': 'namber', 's': ['number']}
 
-        """            
+        """
         if not isinstance(text, str):
             raise BadArgumentError(f"Unsupported type for {text}")
-        
+
         if is_path(text):
             self.spell_path(text, apply=False)
             return
@@ -64,10 +63,10 @@ def spell(self, text: str) -> Iterable[object]:
         if is_url(text):
             content = _fetch_content(text)
             yield from self._spell_text(content)
-        
+
         else:
             yield from self._spell_text(text)
-    
+
     def spelled(self, text: str) -> str:
         """
         Runs spell checking and apply suggestions.
@@ -77,7 +76,7 @@ def spelled(self, text: str) -> str:
         """
         if not isinstance(text, str):
             raise BadArgumentError(f"Unsupported type for {text}")
-    
+
         if is_path(text):
             self.spell_path(text, apply=True)
             return ""
@@ -86,13 +85,13 @@ def spelled(self, text: str) -> str:
             content = _fetch_content(text)
         else:
             content = text
-        
+
         changes = self._spell_text(content)
         return self._apply_suggestion(content, changes)
 
     def spell_path(self, path: str, apply: bool) -> None:
         """
-            Traverse through path and apply spelling
+        Traverse through path and apply spelling
         """
         if not os.path.exists(path):
             logging.warning("Path not found: '%s'", path)
@@ -108,10 +107,10 @@ def spell_path(self, path: str, apply: bool) -> None:
                 fullpath = os.path.join(root, fname)
                 # iterate over changes
                 list(self._spell_file(fullpath, apply))
-            
+
     def spell_url(self, url: str) -> str:
         """
-            Run spell checking for url.
+        Run spell checking for url.
         """
         content = _fetch_content(url)
         changes = self._spell_text(content)
@@ -130,7 +129,7 @@ def _spell_file(self, path: str, apply: bool) -> Iterable[dict]:
 
             if apply:
                 updated = self._apply_suggestion(content, changes)
-                with open(path, 'w') as outfile:
+                with open(path, "w") as outfile:
                     outfile.write(updated)
             else:
                 yield from changes
diff --git a/pyaspeller/word.py b/src/pyaspeller/word.py
similarity index 87%
rename from pyaspeller/word.py
rename to src/pyaspeller/word.py
index b2a0225..7a26445 100644
--- a/pyaspeller/word.py
+++ b/src/pyaspeller/word.py
@@ -1,11 +1,11 @@
 """
 Contains definitions of Word class
 """
-from typing import Optional
 import warnings
+from typing import Optional
 
-from .yandex_speller import YandexSpeller
 from .errors import BadArgumentError
+from .yandex_speller import YandexSpeller
 
 
 class Word:
@@ -15,13 +15,13 @@ class Word:
 
     def __init__(self, *args, **kwargs):
         warnings.warn("Class Word is deprecated. Use Speller().spelled(text) instead")
-        if 'text' in kwargs:
-            text = kwargs.pop('text')
+        if "text" in kwargs:
+            text = kwargs.pop("text")
         else:
             text = args[0]
 
         if len(text.split()) > 1:
-            msg = 'Bad argument. Multiple words were detected.'
+            msg = "Bad argument. Multiple words were detected."
             raise BadArgumentError(msg)
 
         self._spell_text = YandexSpeller(*args[1:], **kwargs)._spell_text
@@ -43,7 +43,7 @@ def correct(self) -> bool:
     def variants(self) -> Optional[str]:
         if self.correct:
             return None
-        return self.answer[0]['s']
+        return self.answer[0]["s"]
 
     @property
     def spellsafe(self) -> Optional[str]:
diff --git a/pyaspeller/yandex_speller.py b/src/pyaspeller/yandex_speller.py
similarity index 85%
rename from pyaspeller/yandex_speller.py
rename to src/pyaspeller/yandex_speller.py
index c3bae21..f40e414 100644
--- a/pyaspeller/yandex_speller.py
+++ b/src/pyaspeller/yandex_speller.py
@@ -16,26 +16,41 @@ class YandexSpeller(Speller):
     """
     Yandex speller implementation.
     """
-    _supported_langs = {'en', 'ru', 'uk'}
 
-    def __init__(self, format_text=None, lang=None, config_path=None,
-                 dictionary=None, report_type=None, max_requests=2,
-                 is_debug=False, check_yo=False, ignore_urls=False,
-                 ignore_tags=False, ignore_capitalization=False,
-                 ignore_digits=False, ignore_latin=False,
-                 ignore_roman_numerals=False, ignore_uppercase=False,
-                 find_repeat_words=False, flag_latin=False, by_words=False):
+    _supported_langs = {"en", "ru", "uk"}
+
+    def __init__(
+        self,
+        format_text=None,
+        lang=None,
+        config_path=None,
+        dictionary=None,
+        report_type=None,
+        max_requests=2,
+        is_debug=False,
+        check_yo=False,
+        ignore_urls=False,
+        ignore_tags=False,
+        ignore_capitalization=False,
+        ignore_digits=False,
+        ignore_latin=False,
+        ignore_roman_numerals=False,
+        ignore_uppercase=False,
+        find_repeat_words=False,
+        flag_latin=False,
+        by_words=False,
+    ):
 
         self._lang = None
         self.lang = lang or self._supported_langs
 
-        if format_text == 'auto' or not format_text:
-            self._format = 'plain'
+        if format_text == "auto" or not format_text:
+            self._format = "plain"
         else:
             self._format = format_text
-        self._config_path = config_path or ''
+        self._config_path = config_path or ""
         self._dictionary = dictionary or {}
-        self._report_type = report_type or 'console'
+        self._report_type = report_type or "console"
 
         self._check_yo = check_yo
         self._ignore_urls = ignore_urls
@@ -52,8 +67,7 @@ def __init__(self, format_text=None, lang=None, config_path=None,
         self._max_requests = max_requests
         self._is_debug = is_debug
 
-        self._api_query = 'https://speller.yandex.net/services/' \
-                          'spellservice.json/checkText'
+        self._api_query = "https://speller.yandex.net/services/" "spellservice.json/checkText"
 
     @property
     def format(self):
@@ -89,7 +103,7 @@ def config_path(self):
     @config_path.setter
     def config_path(self, value):
         """Set config_path"""
-        self._config_path = value or ''
+        self._config_path = value or ""
         if not isinstance(self._config_path, str):
             msg = f"config_path must be a string: {self._config_path}"
             raise BadArgumentError(msg)
@@ -115,7 +129,7 @@ def report_type(self):
     @report_type.setter
     def report_type(self, value):
         """Set report_type"""
-        self._report_type = value or 'console'
+        self._report_type = value or "console"
 
     @property
     def check_yo(self):
@@ -248,13 +262,8 @@ def is_debug(self, value):
         self._is_debug = value
 
     def _spell_text(self, text: str) -> Iterable[object]:
-        lang = ','.join(self._lang)
-        data = {
-            'text': text,
-            'options': self.api_options,
-            'lang': lang,
-            'format': self.format
-        }
+        lang = ",".join(self._lang)
+        data = {"text": text, "options": self.api_options, "lang": lang, "format": self.format}
         response = requests.post(url=self._api_query, data=data).json()
 
         args = requests.compat.urlencode(data)
@@ -264,9 +273,9 @@ def _spell_text(self, text: str) -> Iterable[object]:
 
     def _apply_suggestion(self, text: str, changes: Iterable[dict]) -> str:
         for change in changes:
-            if change['s']:
-                word = change['word']
-                suggestion = change['s'][0]
+            if change["s"]:
+                word = change["word"]
+                suggestion = change["s"][0]
                 text = text.replace(word, suggestion)
         return text
 
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index d052540..0000000
--- a/tox.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[tox]
-envlist = py36,py37,py38,pypy
-
-[testenv]
-deps = -rrequirements-dev.txt
-commands = py.test tests --ignore=.DS_Store


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 439b9cd..229b388 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,5 +1,6 @@
 import pytest
-from pyaspeller import _create_args_parser, main, _create_speller
+
+from pyaspeller import _create_args_parser, _create_speller, main
 
 
 @pytest.fixture()
@@ -14,4 +15,4 @@ def speller(argparser):
 
 
 def test_default_speller(speller):
-    assert speller.format == 'plain', 'Bad default format: ' + speller.format
+    assert speller.format == "plain", "Bad default format: " + speller.format
diff --git a/tests/test_speller.py b/tests/test_speller.py
index 268bef2..7fb7c9d 100644
--- a/tests/test_speller.py
+++ b/tests/test_speller.py
@@ -1,7 +1,8 @@
 import pytest
-from pyaspeller import YandexSpeller
 from pyaspeller.errors import BadArgumentError
 
+from pyaspeller import YandexSpeller
+
 
 @pytest.fixture
 def speller():
@@ -10,199 +11,184 @@ def speller():
 
 def test_bad_single_lang_property():
     with pytest.raises(BadArgumentError):
-        YandexSpeller(lang='qwe')
+        YandexSpeller(lang="qwe")
 
 
 def test_correct_lang_property():
-    sp = YandexSpeller(lang=('ru', 'en'))
-    assert sp.lang == ['ru', 'en'], 'Bad language'
+    sp = YandexSpeller(lang=("ru", "en"))
+    assert sp.lang == ["ru", "en"], "Bad language"
 
 
 def test_correct_single_lang_property():
-    sp = YandexSpeller(lang=('en',))
-    assert sp.lang == ['en'], 'Bad language'
+    sp = YandexSpeller(lang=("en",))
+    assert sp.lang == ["en"], "Bad language"
 
 
 def test_correct_single_lang_str_property():
-    sp = YandexSpeller(lang='uk')
-    assert sp.lang == ['uk'], 'Bad language'
+    sp = YandexSpeller(lang="uk")
+    assert sp.lang == ["uk"], "Bad language"
 
 
 def test_check_lang_property(speller):
-    speller.lang = 'en'
-    assert speller.lang == ['en'], 'Bad language'
+    speller.lang = "en"
+    assert speller.lang == ["en"], "Bad language"
 
 
 def test_param_max_requests(speller):
-    assert speller.max_requests == 2, 'Bad default max_requests'
+    assert speller.max_requests == 2, "Bad default max_requests"
     speller.max_requests = 4
-    assert speller.max_requests == 4, 'Bad max_requests'
+    assert speller.max_requests == 4, "Bad max_requests"
 
 
 def test_param_is_debug(speller):
-    assert not speller.is_debug, 'Bad default is_debug'
+    assert not speller.is_debug, "Bad default is_debug"
     speller.max_requests = True
-    assert speller.max_requests, 'Bad is_debug'
+    assert speller.max_requests, "Bad is_debug"
 
 
 def test_param_format(speller):
-    assert speller.format == 'plain', \
-        'Bad default format: ' + str(speller.format)
-    speller.format = 'html'
-    assert speller.format == 'html', 'Bad format: ' + str(speller.format)
+    assert speller.format == "plain", "Bad default format: " + str(speller.format)
+    speller.format = "html"
+    assert speller.format == "html", "Bad format: " + str(speller.format)
 
 
 def test_param_config_path(speller):
-    assert speller.config_path == '', 'Bad default config_path: ' + \
-                                      str(speller.config_path)
-    speller.config_path = '/path/to/config'
-    assert speller.config_path == '/path/to/config', 'Bad config_path: ' + \
-                                                     str(speller.config_path)
+    assert speller.config_path == "", "Bad default config_path: " + str(speller.config_path)
+    speller.config_path = "/path/to/config"
+    assert speller.config_path == "/path/to/config", "Bad config_path: " + str(speller.config_path)
 
     with pytest.raises(BadArgumentError):
         speller.config_path = ["/path"]
 
 
 def test_param_dictionary(speller):
-    assert speller.dictionary == {}, 'Bad default dictionary: ' + \
-                                     str(speller.dictionary)
-    speller.dictionary = {'answer': 42}
-    assert speller.dictionary == {'answer': 42}, 'Bad dictionary: ' + \
-                                                 str(speller.dictionary)
+    assert speller.dictionary == {}, "Bad default dictionary: " + str(speller.dictionary)
+    speller.dictionary = {"answer": 42}
+    assert speller.dictionary == {"answer": 42}, "Bad dictionary: " + str(speller.dictionary)
 
     with pytest.raises(BadArgumentError):
         speller.dictionary = ["/path"]
 
 
 def test_param_report_type(speller):
-    assert speller.report_type == 'console', 'Bad default report_type'
-    speller.report_type = 'file'
-    assert speller.report_type == 'file', \
-        'report_type not setted: ' + str(speller.report_type)
+    assert speller.report_type == "console", "Bad default report_type"
+    speller.report_type = "file"
+    assert speller.report_type == "file", "report_type not setted: " + str(speller.report_type)
 
-    speller.report_type = ''
-    assert speller.report_type == 'console', \
-        'Bad report_type: ' + str(speller.report_type)
+    speller.report_type = ""
+    assert speller.report_type == "console", "Bad report_type: " + str(speller.report_type)
 
 
 def test_param_check_yo(speller):
-    assert speller.check_yo is False, 'Bad default check_yo'
+    assert speller.check_yo is False, "Bad default check_yo"
     speller.check_yo = True
-    assert speller.check_yo, 'Bad check_yo: ' + str(speller.check_yo)
+    assert speller.check_yo, "Bad check_yo: " + str(speller.check_yo)
 
 
 def test_param_ignore_tags(speller):
-    assert not speller.ignore_tags, 'Bad default ignore_tags'
+    assert not speller.ignore_tags, "Bad default ignore_tags"
     speller.ignore_tags = True
-    assert speller.ignore_tags, 'Bad ignore_tags: ' + str(speller.ignore_tags)
+    assert speller.ignore_tags, "Bad ignore_tags: " + str(speller.ignore_tags)
 
 
 def test_param_ignore_uppercase(speller):
-    assert not speller.api_options & 1, 'Bad ignore_uppercase option'
-    assert not speller.ignore_uppercase, 'Bad default ignore_uppercase'
+    assert not speller.api_options & 1, "Bad ignore_uppercase option"
+    assert not speller.ignore_uppercase, "Bad default ignore_uppercase"
     speller.ignore_uppercase = True
-    assert speller.api_options & 1, 'Bad ignore_uppercase option'
+    assert speller.api_options & 1, "Bad ignore_uppercase option"
 
 
 def test_param_ignore_digits(speller):
-    assert not speller.api_options & 2, 'Bad ignore_digits option'
-    assert not speller.ignore_digits, 'Bad default ignore_digits'
+    assert not speller.api_options & 2, "Bad ignore_digits option"
+    assert not speller.ignore_digits, "Bad default ignore_digits"
     speller.ignore_digits = True
-    assert speller.api_options & 2, 'Bad ignore_digits option'
+    assert speller.api_options & 2, "Bad ignore_digits option"
 
 
 def test_param_ignore_urls(speller):
-    assert not speller.api_options & 4, 'Bad ignore_urls option'
-    assert not speller.ignore_urls, 'Bad default ignore_urls'
+    assert not speller.api_options & 4, "Bad ignore_urls option"
+    assert not speller.ignore_urls, "Bad default ignore_urls"
     speller.ignore_urls = True
-    assert speller.api_options & 4, 'Bad ignore_urls option'
+    assert speller.api_options & 4, "Bad ignore_urls option"
 
 
 def test_param_find_repeat_words(speller):
-    assert not speller.api_options & 8, 'Bad find_repeat_words option'
-    assert not speller.find_repeat_words, 'Bad default find_repeat_words'
+    assert not speller.api_options & 8, "Bad find_repeat_words option"
+    assert not speller.find_repeat_words, "Bad default find_repeat_words"
     speller.find_repeat_words = True
-    assert speller.api_options & 8, 'Bad find_repeat_words option'
+    assert speller.api_options & 8, "Bad find_repeat_words option"
 
 
 def test_param_ignore_latin(speller):
-    assert not speller.api_options & 16, 'Bad ignore_latin option'
-    assert not speller.ignore_latin, 'Bad default ignore_latin'
+    assert not speller.api_options & 16, "Bad ignore_latin option"
+    assert not speller.ignore_latin, "Bad default ignore_latin"
     speller.ignore_latin = True
-    assert speller.api_options & 16, 'Bad ignore_latin option'
+    assert speller.api_options & 16, "Bad ignore_latin option"
 
 
 def test_param_flag_latin(speller):
-    assert not speller.api_options & 128, 'Bad flag_latin option'
-    assert not speller.flag_latin, 'Bad default flag_latin'
+    assert not speller.api_options & 128, "Bad flag_latin option"
+    assert not speller.flag_latin, "Bad default flag_latin"
     speller.flag_latin = True
-    assert speller.api_options & 128, 'Bad flag_latin option'
+    assert speller.api_options & 128, "Bad flag_latin option"
 
 
 def test_param_by_words(speller):
-    assert not speller.api_options & 256, 'Bad by_words option'
-    assert not speller.by_words, 'Bad default by_words'
+    assert not speller.api_options & 256, "Bad by_words option"
+    assert not speller.by_words, "Bad default by_words"
     speller.by_words = True
-    assert speller.api_options & 256, 'Bad by_words option'
+    assert speller.api_options & 256, "Bad by_words option"
 
 
 def test_param_ignore_capitalization(speller):
-    assert not speller.api_options & 512, 'Bad ignore_capitalization option'
-    assert not speller.ignore_capitalization, \
-        'Bad default ignore_capitalization'
+    assert not speller.api_options & 512, "Bad ignore_capitalization option"
+    assert not speller.ignore_capitalization, "Bad default ignore_capitalization"
     speller.ignore_capitalization = True
-    assert speller.api_options & 512, 'Bad ignore_capitalization option'
+    assert speller.api_options & 512, "Bad ignore_capitalization option"
 
 
 def test_param_ignore_roman_numerals(speller):
-    assert not speller.api_options & 2048, 'Bad ignore_roman_numerals option'
-    assert not speller.ignore_roman_numerals, \
-        'Bad default ignore_capitalization'
+    assert not speller.api_options & 2048, "Bad ignore_roman_numerals option"
+    assert not speller.ignore_roman_numerals, "Bad default ignore_capitalization"
     speller.ignore_roman_numerals = True
-    assert speller.api_options & 2048, 'Bad ignore_roman_numerals option'
+    assert speller.api_options & 2048, "Bad ignore_roman_numerals option"
 
 
 def test_spelled(speller):
     result = speller.spelled("tesst message")
-    assert result == 'test message'
+    assert result == "test message"
 
 
-@pytest.mark.parametrize('bad_argument', [
-    0,
-    True,
-    None,
-    ["tesst "],
-    {'bad': 'arg'}
-])
+@pytest.mark.parametrize("bad_argument", [0, True, None, ["tesst "], {"bad": "arg"}])
 def test_spelled_list_of_strings(speller, bad_argument):
     with pytest.raises(BadArgumentError):
         speller.spelled(bad_argument)
 
 
 def test_spell_path(speller, tmpdir):
-    p = tmpdir.join('file.txt')
-    p.write('tesst message')
+    p = tmpdir.join("file.txt")
+    p.write("tesst message")
 
     speller.spell_path(str(tmpdir), apply=True)
 
-    assert p.read() == 'test message'
+    assert p.read() == "test message"
 
-    speller.spell_path('./bad_path_doesnt_raise_error', apply=False)
+    speller.spell_path("./bad_path_doesnt_raise_error", apply=False)
 
 
 def test_spell_path_without_applying(speller, tmpdir):
-    p = tmpdir.join('file.txt')
-    p.write('tesst message')
+    p = tmpdir.join("file.txt")
+    p.write("tesst message")
 
     speller.spell_path(str(tmpdir), apply=False)
 
-    assert p.read() == 'tesst message'
+    assert p.read() == "tesst message"
 
 
 def test_spell_path_file(speller, tmpdir):
-    p = tmpdir.join('file.txt')
-    p.write('tesst message')
+    p = tmpdir.join("file.txt")
+    p.write("tesst message")
 
     speller.spell_path(p, apply=True)
 
-    assert p.read() == 'test message'
+    assert p.read() == "test message"
diff --git a/tests/test_word.py b/tests/test_word.py
index 45aa8b0..efe2752 100644
--- a/tests/test_word.py
+++ b/tests/test_word.py
@@ -1,23 +1,23 @@
 import pytest
+from pyaspeller.errors import BadArgumentError
 
 from pyaspeller import Word
-from pyaspeller.errors import BadArgumentError
 
 
 def test_correct_word():
-    w = Word('test')
+    w = Word("test")
     assert w.correct
     assert not w.variants
     assert not w.spellsafe
 
 
 def test_incorrect_word():
-    w = Word('taxx')
+    w = Word("taxx")
     assert not w.correct
-    assert w.variants == [u'tax', u'texx', u'tixx']
-    assert w.spellsafe == 'tax'
+    assert w.variants == ["tax", "texx", "tixx"]
+    assert w.spellsafe == "tax"
 
 
 def test_several_words():
     with pytest.raises(BadArgumentError):
-        Word('some text')
+        Word("some text")