diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5145e29 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.py] +indent_size = 4 + +[*.{md,txt,rst}] +trim_trailing_whitespace = false diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e31f433 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length=120 +exclude=.git,*tests* diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..ff6b0ca --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +--- +name: Lint + +on: + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: windows-latest + strategy: + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + steps: + - + name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - + uses: actions/checkout@v2 + - + name: dependencies + run: | + python -m pip install poetry==1.1.7 + poetry install + - + name: install hooks + run: poetry run pre-commit install-hooks + - + name: flake8 + run: poetry run pre-commit run flake8 --all-files --show-diff-on-failure + - + name: pylint + run: poetry run pre-commit run pylint --all-files --show-diff-on-failure + - + name: mypy + run: poetry run pre-commit run mypy --all-files --show-diff-on-failure diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 0000000..6a37335 --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,32 @@ +--- +name: PyPI upload + +on: + push: + tags: + - '*' + +jobs: + build: + runs-on: windows-latest + steps: + - + name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: "3.9" + - + name: Checkout + uses: actions/checkout@v2 + - + name: dependencies + run: | + python -m pip install poetry==1.1.7 + - + name: build + run: poetry build + - + name: publish + run: | + poetry config http-basic.pypi ${{ secrets.PYPI_LOGIN }} ${{ secrets.PYPI_PASS }} + poetry publish diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..f19dd7d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,34 @@ +--- +name: Test + +on: + pull_request: + branches: [ main ] + +jobs: + tests: + runs-on: windows-latest + env: + SOURCES_DIR: sources + strategy: + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + steps: + - + name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - + uses: actions/checkout@v2 + - + name: Install dependencies + run: | + python -m pip install poetry==1.1.7 + poetry install + - + name: pytest + run: poetry run pytest -vv diff --git a/.gitignore b/.gitignore index 01881da..56a472b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,7 @@ -# Robots -log.html -report.html -output.xml -# idea +.html +.xml .idea/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -14,7 +12,6 @@ __pycache__/ # Distribution / packaging .Python -env/ build/ develop-eggs/ dist/ @@ -26,9 +23,13 @@ lib64/ parts/ sdist/ var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg +MANIFEST # PyInstaller # Usually these files are written by a python script from a template @@ -43,13 +44,16 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml -*,cover +*.cover +*.py,cover .hypothesis/ +.pytest_cache/ # Translations *.mo @@ -58,6 +62,8 @@ coverage.xml # Django stuff: *.log local_settings.py +db.sqlite3 +db.sqlite3-journal # Flask stuff: instance/ @@ -72,24 +78,56 @@ docs/_build/ # PyBuilder target/ -# IPython Notebook +# Jupyter Notebook .ipynb_checkpoints +# IPython +profile_default/ +ipython_config.py + # pyenv .python-version -# celery beat schedule file +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff celerybeat-schedule +celerybeat.pid -# dotenv -.env +# SageMath parsed files +*.sage.py -# virtualenv +# Environments +.env +.venv +env/ venv/ ENV/ +env.bak/ +venv.bak/ # Spyder project settings .spyderproject +.spyproject # Rope project settings .ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..bbab900 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,47 @@ +repos: + - + repo: local + hooks: +# - +# id: isort +# name: isort +# entry: isort +# language: system +# files: ^(winregistry|test)/.+\.py$ + - + id: black + name: black + entry: black + language: system + files: ^(winregistry|test)/.+\.py$ + - + id: flake8 + name: flake8 + entry: flake8 + language: system + files: ^(winregistry|test)/.+\.py$ + - + id: pylint + name: pylint + entry: pylint + language: system + files: ^(winregistry|test)/.+\.py$ + - + id: mypy + name: mypy + entry: mypy + language: system + files: ^(winregistry|test)/.+\.py$ + - + repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v3.4.0" + hooks: + - id: check-added-large-files + - id: check-json + - id: check-merge-conflict + - id: check-yaml + - id: detect-private-key + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: forbid-new-submodules + - id: trailing-whitespace diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..d801e55 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,39 @@ +[mypy] +ignore_missing_imports = True + +# Disallow dynamic typing +disallow_any_unimported = False +disallow_any_generics = True +disallow_subclassing_any = False + +# Disallow untyped definitions and calls +disallow_untyped_defs = True +disallow_incomplete_defs = True + +# None and optional handling +no_implicit_optional = True + +# Configuring warnings +warn_unused_ignores = True +warn_return_any = True +warn_redundant_casts = True + +# Misc things +strict_equality = True + +# Config file +warn_unused_configs = True + +[mypy-tests.*] +ignore_errors = True + +# ignore return type for routers +[mypy-app.routers.*] +disallow_incomplete_defs = False +disallow_untyped_defs = False + +[pydantic-mypy] +init_forbid_extra = True +init_typed = True +warn_required_dynamic_aliases = True +warn_untyped_fields = True diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..fdf0e01 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,741 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "astroid" +version = "2.6.2" +description = "An abstract syntax tree for Python with inference support." +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} +wrapt = ">=1.11,<1.13" + +[[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.2.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"] +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"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "black" +version = "21.6b0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.8.1,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +python2 = ["typed-ast (>=1.4.2)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cfgv" +version = "3.3.0" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "click" +version = "8.0.1" +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 = "distlib" +version = "0.3.2" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "identify" +version = "2.2.10" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.extras] +license = ["editdistance-s"] + +[[package]] +name = "importlib-metadata" +version = "4.6.1" +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)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "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.9.2" +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 = "lazy-object-proxy" +version = "1.6.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy" +version = "0.910" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +toml = "*" +typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<1.5.0)"] + +[[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 = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "pre-commit" +version = "2.13.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "py" +version = "1.10.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.*" + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pylint" +version = "2.9.3" +description = "python code static checker" +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +astroid = ">=2.6.2,<2.7" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.7" +toml = ">=0.7.1" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.4" +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,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "regex" +version = "2021.7.6" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "robotframework" +version = "4.0.3" +description = "Generic automation framework for acceptance testing and robotic process automation (RPA)" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[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 = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "virtualenv" +version = "20.4.7" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.1,<1" +filelock = ">=3.0.0,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + +[[package]] +name = "wrapt" +version = "1.12.1" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "zipp" +version = "3.5.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.7" +content-hash = "1596ad6c3fc5bc2882b2d43c471b281de2f6ee72d11b00d9a23c120c1f4125f1" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +astroid = [ + {file = "astroid-2.6.2-py3-none-any.whl", hash = "sha256:606b2911d10c3dcf35e58d2ee5c97360e8477d7b9f3efc3f24811c93e6fc2cd9"}, + {file = "astroid-2.6.2.tar.gz", hash = "sha256:38b95085e9d92e2ca06cf8b35c12a74fa81da395a6f9e65803742e6509c05892"}, +] +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.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +black = [ + {file = "black-21.6b0-py3-none-any.whl", hash = "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"}, + {file = "black-21.6b0.tar.gz", hash = "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04"}, +] +cfgv = [ + {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, + {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, +] +click = [ + {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, + {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +distlib = [ + {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, + {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +flake8 = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] +identify = [ + {file = "identify-2.2.10-py2.py3-none-any.whl", hash = "sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421"}, + {file = "identify-2.2.10.tar.gz", hash = "sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.6.1-py3-none-any.whl", hash = "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"}, + {file = "importlib_metadata-4.6.1.tar.gz", hash = "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac"}, +] +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.9.2-py3-none-any.whl", hash = "sha256:eed17b53c3e7912425579853d078a0832820f023191561fcee9d7cae424e0813"}, + {file = "isort-5.9.2.tar.gz", hash = "sha256:f65ce5bd4cbc6abdfbe29afc2f0245538ab358c14590912df638033f157d555e"}, +] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, +] +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.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, + {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, + {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, + {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, + {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, + {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, + {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, + {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, + {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, + {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, + {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, + {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, + {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, + {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, + {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, + {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, + {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, + {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, + {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, + {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, + {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, + {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, + {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, +] +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"}, +] +nodeenv = [ + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, +] +packaging = [ + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +pre-commit = [ + {file = "pre_commit-2.13.0-py2.py3-none-any.whl", hash = "sha256:b679d0fddd5b9d6d98783ae5f10fd0c4c59954f375b70a58cbe1ce9bcf9809a4"}, + {file = "pre_commit-2.13.0.tar.gz", hash = "sha256:764972c60693dc668ba8e86eb29654ec3144501310f7198742a767bec385a378"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] +pyflakes = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] +pylint = [ + {file = "pylint-2.9.3-py3-none-any.whl", hash = "sha256:5d46330e6b8886c31b5e3aba5ff48c10f4aa5e76cbf9002c6544306221e63fbc"}, + {file = "pylint-2.9.3.tar.gz", hash = "sha256:23a1dc8b30459d78e9ff25942c61bb936108ccbe29dd9e71c01dc8274961709a"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +regex = [ + {file = "regex-2021.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222"}, + {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407"}, + {file = "regex-2021.7.6-cp36-cp36m-win32.whl", hash = "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b"}, + {file = "regex-2021.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb"}, + {file = "regex-2021.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad"}, + {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895"}, + {file = "regex-2021.7.6-cp37-cp37m-win32.whl", hash = "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5"}, + {file = "regex-2021.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f"}, + {file = "regex-2021.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761"}, + {file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068"}, + {file = "regex-2021.7.6-cp38-cp38-win32.whl", hash = "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0"}, + {file = "regex-2021.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4"}, + {file = "regex-2021.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"}, + {file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3"}, + {file = "regex-2021.7.6-cp39-cp39-win32.whl", hash = "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035"}, + {file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"}, + {file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"}, +] +robotframework = [ + {file = "robotframework-4.0.3-py2.py3-none-any.whl", hash = "sha256:cebba6980372762c249d87843a55972873800ffda3bf73c848e95c7cad2eef59"}, + {file = "robotframework-4.0.3.zip", hash = "sha256:93c2107f789fd897f234f4b8f1ba8e7b9f4ef326d9bcbfceb71dda8cc197388c"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] +virtualenv = [ + {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, + {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"}, +] +wrapt = [ + {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, +] +zipp = [ + {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dc32cc6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,70 @@ +[tool.poetry] +name = "winregistry" +version = "1.0.0" +description = "Library aimed at working with Windows registry" +authors = ["Aleksandr Shpak "] +readme = "readme.md" +homepage = "https://github.com/shpaker/winregistry" +repository = "https://github.com/shpaker/winregistry" +keywords = ["windows", "registry", "regedit", "winreg"] +classifiers = [ + 'Programming Language :: Python', + 'Framework :: Robot Framework :: Library', + 'Environment :: Win32 (MS Windows)', + 'Operating System :: Microsoft :: Windows', +] +packages = [ + { include = "winregistry" }, + { include = "winregistry/robot" }, +] + +[tool.poetry.dependencies] +python = "^3.7" + +[tool.poetry.dev-dependencies] +flake8 = "^3.9.2" +pylint = "^2.9.3" +mypy = "^0.910" +pre-commit = "^2.13.0" +pytest = "^6.2.4" +black = "^21.6b0" +robotframework = "^4.0.3" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 120 +verbose = 1 +color = true +exclude = ''' +( + /( + \.eggs + | \.git + | \.mypy_cache + | \.pytest_cache + | \.venv + )/ +) +''' + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +line_length = 120 +src_paths = ["soxyproxy", "test"] +skip = [".mypy_cache", ".pytest_cache", "venv"] + +[tool.pylint.messages_control] +disable = "missing-docstring,no-self-use,logging-fstring-interpolation,too-few-public-methods,too-many-arguments,duplicate-code" +extend-ignore = "E203,W503" + +[tool.pylint.format] +max-line-length = "120" +indent-string = " " +good-names = "i, x, y" diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..21c1e4e --- /dev/null +++ b/readme.md @@ -0,0 +1,41 @@ +# winregistry + +Minimalist Python library aimed at working with Windows Registry. + +## Installation + +```bash +pip install winregistry +``` + +## Usage + +```py +from winregistry import WinRegistry + +TEST_REG_PATH = r"HKLM\SOFTWARE\_REMOVE_ME_" + + +if __name__ == "__main__": + with WinRegistry() as client: + client.create_key(TEST_REG_PATH) + client.write_entry(TEST_REG_PATH, "remove_me", "test") + test_entry = client.read_entry(TEST_REG_PATH, "remove_me") + assert test_entry.value == "test" + client.delete_entry(TEST_REG_PATH, "remove_me") +``` + +Usage with ``Robot Testing Framework`` Library +---------------------------------------------- + +``` +*** Settings *** +Library winregistry.robot + +*** Test Cases *** +Valid Login + ${path} = Set Variable HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run + Write Registry Entry ${path} Notepad notepad.exe + ${autorun} = Read Registry Key ${path} + Delete Registry Entry ${path} Notepad +``` diff --git a/readme.rst b/readme.rst deleted file mode 100644 index 1bfa8a9..0000000 --- a/readme.rst +++ /dev/null @@ -1,57 +0,0 @@ -winregistry -=========== - -.. image:: https://badge.fury.io/py/winregistry.svg - :target: https://badge.fury.io/py/winregistry - -.. image:: https://landscape.io/github/shpaker/winregistry/master/landscape.svg?style=flat - :target: https://landscape.io/github/shpaker/winregistry/master - :alt: Code Health - -Minimalist Python library aimed at working with Windows Registry. - -Installation ------------- - -.. code-block:: bash - - pip install winregistry - -Usage ------ - -.. code-block:: python - - >>> from winregistry import WinRegistry as Reg - >>> reg = Reg() - >>> path = r'HKLM\SOFTWARE\remove_me' - >>> reg.create_key(path + r'\test') - >>> True if 'remove_me' in reg.read_key(r'HKLM\SOFTWARE')['keys'] else False - True - >>> reg.write_value(path, 'Value name', b'21', 'REG_BINARY') - >>> reg.read_key(path) - {'keys': ['test'], 'values': [{'value': 'Value name', 'data': b'21... - >>> reg.read_value(path, 'Value name') - {'value': 'Value name', 'data': b'21', 'type': 'REG_BINARY'} - >>> reg.delete_value(path, 'Value name') - >>> reg.delete_key(path + r'\test') - >>> reg.read_key(path) - {'keys': [], 'values': [], 'modify': datetime.datetime(2017, 4, 16... - >>> reg.delete_key(path) - >>> True if 'remove_me' in reg.read_key(r'HKLM\SOFTWARE')['keys'] else False - False - -Usage with ``Robot Testing Framework`` Library ----------------------------------------------- - -.. code-block:: robotframework - - *** Settings *** - Library winregistry.robot - - *** Test Cases *** - Valid Login - ${path} = Set Variable HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run - Write Registry Value ${path} Notepad notepad.exe - ${autorun} = Read Registry Key ${path} - Delete Registry Value ${path} Notepad diff --git a/setup.py b/setup.py deleted file mode 100644 index 58473c5..0000000 --- a/setup.py +++ /dev/null @@ -1,28 +0,0 @@ -from setuptools import setup -# import os - - -# def read(fname): -# return open(os.path.join(os.path.dirname(__file__), fname)).read() - - -setup ( - name='winregistry', - version='0.8.2', - author='Alexander Shpak', - author_email='shpaker@gmail.com', - description=('Library aimed at working with Windows registry'), - # long_description=read('readme.rst'), - keywords='windows registry regedit winreg', - url='https://github.com/shpaker/winregistry', - platforms='windows', - packages=['winregistry', 'winregistry.robot'], - classifiers=[ - 'Programming Language :: Python', - 'Development Status :: 3 - Alpha', - 'Framework :: Robot Framework :: Library', - 'Environment :: Win32 (MS Windows)', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Operating System :: Microsoft :: Windows' - ] -) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..c066296 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,111 @@ +from winreg import KEY_READ, HKEY_LOCAL_MACHINE + +from winregistry import WinRegistry +from winregistry.utils import ( + expand_short_root, + get_access_key, + parse_path, +) + +TEST_REG_PATH = r"HKLM\SOFTWARE\_REMOVE_ME_" + + +def test_expand_short_root() -> None: + root = expand_short_root("HKU") + assert r"HKEY_USERS" == root, root + + +def test_expand_long_root() -> None: + root = expand_short_root("SOME_LONG_STRING") + assert "SOME_LONG_STRING" == root, root + + +def test_get_access_key() -> None: + access_key = get_access_key(KEY_READ, True) + assert access_key + + +def test_parse_path() -> None: + root, path = parse_path(TEST_REG_PATH) + assert root == HKEY_LOCAL_MACHINE, root + assert path == TEST_REG_PATH.lstrip("HKLM\\") + + +def test_get_key_handle() -> None: + with WinRegistry() as reg: + handler = reg.get_handler( + r"HKLM\SOFTWARE", + access=KEY_READ, + key_wow64_32key=False, + ) + assert handler + + +def test_read_entry() -> None: + reg_key = r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" + with WinRegistry() as client: + software_type = client.read_entry(reg_key, "SoftwareType") + assert software_type.value == "System" + + +def test_write_entry() -> None: + with WinRegistry() as client: + client.create_key(TEST_REG_PATH) + client.write_entry(TEST_REG_PATH, "remove_me", "test") + test_entry = client.read_entry(TEST_REG_PATH, "remove_me") + assert test_entry.value == "test" + client.delete_entry(TEST_REG_PATH, "remove_me") + + +def test_delete_entry() -> None: + with WinRegistry() as client: + client.create_key(TEST_REG_PATH) + client.write_entry(TEST_REG_PATH, "remove_me", "test") + client.read_entry(TEST_REG_PATH, "remove_me") + client.delete_entry(TEST_REG_PATH, "remove_me") + try: + client.read_entry(TEST_REG_PATH, "remove_me") + raise AssertionError + except FileNotFoundError: + pass + + +def test_read_key() -> None: + reg_key = r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" + with WinRegistry() as client: + data = client.read_key(reg_key) + assert data.entries + assert data.reg_keys + + +def test_create_key() -> None: + reg_key = r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test\foo" + with WinRegistry() as client: + try: + data = client.read_key(r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test") + raise AssertionError + except FileNotFoundError: + pass + client.create_key(reg_key) + data = client.read_key(r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test") + assert len(data.reg_keys) == 1, data + assert "foo" in data.reg_keys, data + client.delete_key(r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test\foo") + client.delete_key(r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test") + + +def test_delete_key() -> None: + reg_key = r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test" + with WinRegistry() as client: + try: + data = client.read_key(r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test") + raise AssertionError + except FileNotFoundError: + pass + client.create_key(reg_key) + client.delete_key(r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test") + try: + data = client.read_key(r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\test") + raise AssertionError + except FileNotFoundError: + pass diff --git a/winregistry/__init__.py b/winregistry/__init__.py index 4b460a4..e67ca0c 100644 --- a/winregistry/__init__.py +++ b/winregistry/__init__.py @@ -1 +1,11 @@ -from .winregistry import WinRegistry +from winregistry.consts import WinregType, ShortRootAlias +from winregistry.models import RegEntry, RegKey +from winregistry.winregistry import WinRegistry + +__all__ = ( + "WinRegistry", + "RegEntry", + "RegKey", + "WinregType", + "ShortRootAlias", +) diff --git a/winregistry/consts.py b/winregistry/consts.py new file mode 100644 index 0000000..4df0c04 --- /dev/null +++ b/winregistry/consts.py @@ -0,0 +1,27 @@ +import winreg +from enum import Enum, IntEnum + + +class ShortRootAlias(Enum): + HKCR = "HKEY_CLASSES_ROOT" + HKCU = "HKEY_CURRENT_USER" + HKLM = "HKEY_LOCAL_MACHINE" + HKU = "HKEY_USERS" + HKCC = "HKEY_CURRENT_CONFIG" + + +class WinregType(IntEnum): + REG_NONE = winreg.REG_NONE + REG_SZ = winreg.REG_SZ + REG_EXPAND_SZ = winreg.REG_EXPAND_SZ + REG_BINARY = winreg.REG_BINARY + REG_DWORD = winreg.REG_DWORD + REG_DWORD_LITTLE_ENDIAN = winreg.REG_DWORD_LITTLE_ENDIAN + REG_DWORD_BIG_ENDIAN = winreg.REG_DWORD_BIG_ENDIAN + REG_LINK = winreg.REG_LINK + REG_MULTI_SZ = winreg.REG_MULTI_SZ + REG_RESOURCE_LIST = winreg.REG_RESOURCE_LIST + REG_FULL_RESOURCE_DESCRIPTOR = winreg.REG_FULL_RESOURCE_DESCRIPTOR + REG_RESOURCE_REQUIREMENTS_LIST = winreg.REG_RESOURCE_REQUIREMENTS_LIST + REG_QWORD = winreg.REG_QWORD + REG_QWORD_LITTLE_ENDIAN = winreg.REG_QWORD_LITTLE_ENDIAN diff --git a/winregistry/models.py b/winregistry/models.py new file mode 100644 index 0000000..f97caf8 --- /dev/null +++ b/winregistry/models.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Any, List, Optional + +from winregistry.consts import WinregType + + +@dataclass(frozen=True) +class RegEntry: + name: str + reg_key: str + value: Any + type: WinregType + host: Optional[str] = None + + +@dataclass(frozen=True) +class RegKey: + name: str + reg_keys: List[str] + entries: List[RegEntry] + modify_at: datetime diff --git a/winregistry/robot/__init__.py b/winregistry/robot/__init__.py index 92a6c3a..cae8929 100644 --- a/winregistry/robot/__init__.py +++ b/winregistry/robot/__init__.py @@ -1,16 +1,16 @@ -from .keywords import Keywords +from winregistry.robot.keywords import Keywords +ROBOT_LIBRARY_SCOPE = "GLOBAL" -ROBOT_LIBRARY_SCOPE = 'GLOBAL' -class robot(Keywords): - """ RequestsLibrary is a HTTP client keyword library that uses - the requests module from Kenneth Reitz - https://github.com/kennethreitz/requests +class robot(Keywords): # pylint: disable=invalid-name + """ + Minimalist Python library aimed at working with Windows Registry. + https://github.com/shpaker/winregistry Examples: | ${path} | HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run | - | Write Registry Value | ${path} | Notepad | notepad.exe | + | Write Registry Entry | ${path} | Notepad | notepad.exe | | ${autorun} = | Read Registry Key | ${path} | | | Log | ${autorun} | | | - | Delete Registry Value | ${path} | Notepad | | + | Delete Registry Entry | ${path} | Notepad | | """ diff --git a/winregistry/robot/keywords.py b/winregistry/robot/keywords.py index 0db30e1..736ec4c 100644 --- a/winregistry/robot/keywords.py +++ b/winregistry/robot/keywords.py @@ -1,48 +1,69 @@ -""" +""" Class for working with Robot Testing Framework """ +from typing import Any -from winregistry import WinRegistry as Reg - +from winregistry import WinRegistry, RegKey, WinregType, RegEntry -class Keywords(object): - def __init__(self, host=None): - self.reg = Reg(host) - - def read_registry_key(self, key, key_wow64_32key=False): - """ Reading registry key - """ - resp = self.reg.read_key(key, key_wow64_32key) +class Keywords: + def read_registry_key( + self, + key: str, + key_wow64_32key: bool = False, + ) -> RegKey: + """Reading registry key""" + with WinRegistry() as client: + resp = client.read_key(key, key_wow64_32key) return resp + def create_registry_key( + self, + key: str, + key_wow64_32key: bool = False, + ) -> None: + """Creating registry key""" + with WinRegistry() as client: + client.create_key(key, key_wow64_32key) + + def delete_registry_key( + self, + key: str, + key_wow64_32key: bool = False, + ) -> None: + """Deleting registry key""" + with WinRegistry() as client: + client.delete_key(key, key_wow64_32key) + + def read_registry_entry( + self, + key: str, + value: Any, + key_wow64_32key: bool = False, + ) -> RegEntry: + """Reading value from registry""" + with WinRegistry() as client: + resp = client.read_entry(key, value, key_wow64_32key) + return resp - def create_registry_key(self, key, key_wow64_32key=False): - """ Creating registry key - """ - self.reg.create_key(key, key_wow64_32key) - - - def delete_registry_key(self, key, key_wow64_32key=False): - """ Deleting registry key - """ - self.reg.delete_key(key, key_wow64_32key) - - - def read_registry_value(self, key, value, key_wow64_32key=False): - """ Reading value from registry - """ - return self.reg.read_value(key, value, key_wow64_32key) - - - def write_registry_value(self, key, value, data=None, reg_type='REG_SZ', key_wow64_32key=False): - """ Writing (or creating) data in value - """ - self.reg.write_value(key, value, data, reg_type, key_wow64_32key) - - - def delete_registry_value(self, key, value, key_wow64_32key=False): - """ Deleting value from registry - """ - self.reg.delete_value(key, value, key_wow64_32key) - + def write_registry_entry( + self, + key: str, + value: str, + data: Any = None, + reg_type: str = "REG_SZ", + key_wow64_32key: bool = False, + ) -> None: + """Writing (or creating) data in value""" + with WinRegistry() as client: + client.write_entry(key, value, data, WinregType[reg_type], key_wow64_32key) + + def delete_registry_entry( + self, + key: str, + value: str, + key_wow64_32key: bool = False, + ) -> None: + """Deleting value from registry""" + with WinRegistry() as client: + client.delete_entry(key, value, key_wow64_32key) diff --git a/winregistry/utils.py b/winregistry/utils.py new file mode 100644 index 0000000..0c2926a --- /dev/null +++ b/winregistry/utils.py @@ -0,0 +1,34 @@ +import winreg +from typing import Tuple +from winreg import KEY_WOW64_32KEY, KEY_WOW64_64KEY, HKEYType + +from winregistry import ShortRootAlias + + +def expand_short_root( + root: str, +) -> str: + try: + root = ShortRootAlias[root].value + except KeyError: + pass + return root + + +def get_access_key( + access: int, + key_wow64_32key: bool = False, +) -> int: + x64_key = KEY_WOW64_32KEY if key_wow64_32key else KEY_WOW64_64KEY + return access | x64_key + + +def parse_path( + path: str, +) -> Tuple[HKEYType, str]: + _root, key_path = path.split("\\", maxsplit=1) + _root = expand_short_root(_root.upper()) + reg_root = getattr(winreg, _root) + if not key_path: + raise ValueError('Not found key in "{}"'.format(path)) + return reg_root, key_path diff --git a/winregistry/winregistry.py b/winregistry/winregistry.py index c052d2f..f063ce6 100644 --- a/winregistry/winregistry.py +++ b/winregistry/winregistry.py @@ -1,214 +1,160 @@ -""" WinRegistry - ~~~~~~~~~~~ - - Usage:: - >>> from winregistry import WinRegistry as Reg - >>> reg = Reg() - >>> path = r'HKLM\SOFTWARE\remove_me' - >>> reg.create_key(path + r'\test') - >>> True if 'remove_me' in reg.read_key(r'HKLM\SOFTWARE')['keys'] else False - True - >>> reg.write_value(path, 'Value name', b'21', 'REG_BINARY') - >>> reg.read_key(path) - {'keys': ['test'], 'values': [{'value': 'Value name', 'data': b'21... - >>> reg.read_value(path, 'Value name') - {'value': 'Value name', 'data': b'21', 'type': 'REG_BINARY'} - >>> reg.delete_value(path, 'Value name') - >>> reg.delete_key(path + r'\test') - >>> reg.read_key(path) - {'keys': [], 'values': [], 'modify': datetime.datetime(2017, 4, 16... - >>> reg.delete_key(path) - >>> True if 'remove_me' in reg.read_key(r'HKLM\SOFTWARE')['keys'] else False - False -""" from datetime import datetime, timedelta -import winreg - - -WINREG_TYPES = ['REG_NONE', # 0 == winreg.REG_NONE - 'REG_SZ', # 1 == winreg.REG_SZ - 'REG_EXPAND_SZ', # 2 == winreg.REG_EXPAND_SZ - 'REG_BINARY', # 3 == winreg.REG_BINARY - 'REG_DWORD', # 4 == winreg.REG_DWORD - # 4 == winreg.REG_DWORD_LITTLE_ENDIAN - # 'REG_DWORD_LITTLE_ENDIAN', - 'REG_DWORD_BIG_ENDIAN', # 5 == winreg.REG_DWORD_BIG_ENDIAN - 'REG_LINK', # 6 == winreg.REG_LINK - 'REG_MULTI_SZ', # 7 == winreg.REG_MULTI_SZ - 'REG_RESOURCE_LIST', # 8 == winreg.REG_RESOURCE_LIST - # 9 == winreg.REG_FULL_RESOURCE_DESCRIPTOR - 'REG_FULL_RESOURCE_DESCRIPTOR', - # 10 == winreg.REG_RESOURCE_REQUIREMENTS_LIST: - 'REG_RESOURCE_REQUIREMENTS_LIST', - 'REG_QWORD' # 11 == winreg.REG_QWORD - # 11 == winreg.REG_QWORD_LITTLE_ENDIAN - # 'REG_QWORD_LITTLE_ENDIAN' -] - -SHORT_ROOTS = { - 'HKCR': 'HKEY_CLASSES_ROOT', - 'HKCU': 'HKEY_CURRENT_USER', - 'HKLM': 'HKEY_LOCAL_MACHINE', - 'HKU': 'HKEY_USERS', - 'HKCC': 'HKEY_CURRENT_CONFIG'} - - -class WinRegistry(object): - """ Minimalist Python library aimed at working with Windows registry - """ - - def __init__(self, host=None): - self.host = host - self.root = None - self.root_handle = None - - - def close(self): - ''' Close registry handle - ''' - self.root_handle.Close() - - - def read_value(self, key, value, key_wow64_32key=False): - ''' Read named value in registry - ''' - try: - handle = self._get_handle(key, winreg.KEY_READ, key_wow64_32key) - reg_value = winreg.QueryValueEx(handle, value) - handle.Close() - except: - raise - reg_type = WINREG_TYPES[reg_value[1]] - data = {'value': value, 'data': reg_value[0], 'type': reg_type} - return data - - - def write_value(self, key, value, data=None, reg_type='REG_SZ', key_wow64_32key=False): - ''' Write data in named value in registry - ''' - try: - handle = self._get_handle(key, winreg.KEY_SET_VALUE, key_wow64_32key) - winreg.SetValueEx(handle, value, 0, getattr(winreg, reg_type), data) - handle.Close() - except: - raise - - - def delete_value(self, key, value, key_wow64_32key=False): - ''' Delete named value in registry - ''' - try: - handle = self._get_handle(key, winreg.KEY_SET_VALUE, key_wow64_32key) - winreg.DeleteValue(handle, value) - handle.Close() - except: - raise - +from typing import Any, Optional, Union +from winreg import ( + KEY_READ, + KEY_SET_VALUE, + KEY_WRITE, + ConnectRegistry, + CreateKeyEx, + DeleteKey, + DeleteValue, + EnumKey, + EnumValue, + HKEYType, + OpenKey, + QueryInfoKey, + QueryValueEx, + SetValueEx, +) + +from winregistry.consts import WinregType +from winregistry.models import RegEntry, RegKey +from winregistry.utils import get_access_key, parse_path + + +class WinRegistry: + def __init__( + self, + host: Optional[str] = None, + ) -> None: + self.host: Optional[str] = host + self._client: Optional[HKEYType] = None + self._handler = None + self._root: Optional[HKEYType] = None + + def get_handler( + self, + key: str, + access: int, + key_wow64_32key: bool, + ) -> HKEYType: + root, path = parse_path(key) + access_key = get_access_key(access, key_wow64_32key) + if not self._client or root != self._root: + self._client = ConnectRegistry(self.host, root) + key_handle = OpenKey( + key=self._client, + sub_key=path, + reserved=0, + access=access_key, + ) + return key_handle - def read_key(self, key, key_wow64_32key=False): - ''' Read named key in registry - ''' - resp = {'keys': [], 'values': []} + def __enter__(self) -> "WinRegistry": + return self - try: - handle = self._get_handle(key, winreg.KEY_READ, key_wow64_32key) - keys_num, values_num, modify = winreg.QueryInfoKey(handle) - resp['modify'] = datetime(1601, 1, 1) + timedelta(microseconds=modify/10) - except: + def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore + if self._client: + self._client.Close() + if exc_val: raise + def read_entry( + self, + reg_key: str, + name: str, + key_wow64_32key: bool = False, + ) -> RegEntry: + handle = self.get_handler(reg_key, KEY_READ, key_wow64_32key) + raw_value, raw_type = QueryValueEx(handle, name) + return RegEntry( + reg_key=reg_key, + name=name, + value=raw_value, + type=WinregType(raw_type), + host=self.host, + ) + + def write_entry( + self, + reg_key: str, + name: str, + value: Any = None, + reg_type: Union[WinregType, int] = WinregType.REG_SZ, + key_wow64_32key: bool = False, + ) -> None: + if isinstance(reg_type, int): + reg_type = WinregType(reg_type) + handle = self.get_handler(reg_key, KEY_SET_VALUE, key_wow64_32key) + SetValueEx(handle, name, 0, reg_type.value, value) + + def delete_entry( + self, + key: str, + name: str, + key_wow64_32key: bool = False, + ) -> None: + handle = self.get_handler(key, KEY_SET_VALUE, key_wow64_32key) + DeleteValue(handle, name) + + def read_key( + self, + name: str, + key_wow64_32key: bool = False, + ) -> RegKey: + handle = self.get_handler(name, KEY_READ, key_wow64_32key) + keys_num, values_num, modify = QueryInfoKey(handle) + modify_at = datetime(1601, 1, 1) + timedelta(microseconds=modify / 10) + keys = list() + entries = list() for key_i in range(0, keys_num): - resp['keys'].append(winreg.EnumKey(handle, key_i)) - + keys.append(EnumKey(handle, key_i)) for key_i in range(0, values_num): - value = {} - value['value'], value['data'], value['type'] = winreg.EnumValue(handle, key_i) - value['type'] = WINREG_TYPES[value['type']] - resp['values'].append(value) - - handle.Close() - - return resp - - - def create_key(self, key, key_wow64_32key=False): - ''' Create named key in registry - ''' - handle = None - subkeys = key.split('\\') + entry_name, value, raw_type = EnumValue(handle, key_i) + entries.append( + RegEntry( + reg_key=name, + name=entry_name, + value=value, + type=WinregType(raw_type), + host=self.host, + ) + ) + return RegKey( + name=name, + reg_keys=keys, + entries=entries, + modify_at=modify_at, + ) + + def create_key( + self, + name: str, + key_wow64_32key: bool = False, + ) -> None: + handler = None + sub_keys = name.split("\\") i = 0 - - while (i < len(subkeys)) and (not handle): + while i < len(sub_keys) and not handler: try: - current = '\\'.join(subkeys[:len(subkeys) - i]) - handle = self._get_handle(current, winreg.KEY_WRITE, key_wow64_32key) + current = "\\".join(sub_keys[: len(sub_keys) - i]) + handler = self.get_handler(current, KEY_WRITE, key_wow64_32key) except FileNotFoundError: i += 1 - - tail = '\\'.join(subkeys[len(subkeys) - i:]) - winreg.CreateKeyEx(handle, tail, 0, self._get_access(winreg.KEY_WRITE)) - handle.Close() - - - def delete_key(self, key, key_wow64_32key=False): - ''' Delete named key from registry - ''' - try: - parental, subkey = self._parse_subkey(key) - - handle = self._get_handle(parental, winreg.KEY_WRITE, key_wow64_32key) - - winreg.DeleteKey(handle, subkey) - - handle.Close() - except: - raise - - def _get_handle(self, key, access, key_wow64_32key=False): - key_handle = None - root, path = self._parse_root(key) - access = self._get_access(access, key_wow64_32key) - - try: - if root != self.root or not self.root_handle: - self.root_handle = winreg.ConnectRegistry(self.host, root) - key_handle = winreg.OpenKey(self.root_handle, path, 0, access) - except: - raise - - return key_handle - - - def _get_access(self, access, key_wow64_32key=False): - x64_key = winreg.KEY_WOW64_32KEY if key_wow64_32key else winreg.KEY_WOW64_64KEY - return access | x64_key - - - @staticmethod - def _parse_root(path): - try: - _root, key_path = path.split('\\', 1) - except: - raise Exception('Error while parsing registry path "{0}"'.format(path)) - try: - _root = _root.upper() - _root = SHORT_ROOTS[_root] if _root in SHORT_ROOTS else _root - reg_root = getattr(winreg, _root) - except: - raise Exception('None exist root key "{}"'.format(_root)) - if not key_path: - raise Exception('Not found existsing key in "{}"'.format(path)) - return (reg_root, key_path) - - - @staticmethod - def _parse_subkey(key): - try: - parental, subkey = key.rsplit(sep='\\', maxsplit=1) - except: - raise Exception('Error while parsing registry key "{}"'.format(key)) - - if not parental or not subkey: - raise Exception('Not found existsing parental (or child) key in "{}"'.format(key)) - - return (parental, subkey) + before_index = len(sub_keys) - i + tail = "\\".join(sub_keys[before_index:]) + CreateKeyEx( + key=handler, # type: ignore + sub_key=tail, + reserved=0, + access=get_access_key(KEY_WRITE), + ) + + def delete_key( + self, + name: str, + key_wow64_32key: bool = False, + ) -> None: + parental, key_name = name.rsplit(sep="\\", maxsplit=1) + handle = self.get_handler(parental, KEY_WRITE, key_wow64_32key) + DeleteKey(handle, key_name)