pax_global_header 0000666 0000000 0000000 00000000064 13711015754 0014516 g ustar 00root root 0000000 0000000 52 comment=db4bbb778669fd11c709919a99f04b09cfe2ff2e
crashtest-0.3.1/ 0000775 0000000 0000000 00000000000 13711015754 0013517 5 ustar 00root root 0000000 0000000 crashtest-0.3.1/.coveragerc 0000664 0000000 0000000 00000000067 13711015754 0015643 0 ustar 00root root 0000000 0000000 [report]
exclude_lines =
raise NotImplementedError
crashtest-0.3.1/.flake8 0000664 0000000 0000000 00000000371 13711015754 0014673 0 ustar 00root root 0000000 0000000 [flake8]
max-line-length = 88
ignore = E501, E203, W503
per-file-ignores = __init__.py:F401
exclude =
.git
__pycache__
setup.py
build
dist
releases
.venv
.tox
.mypy_cache
.pytest_cache
.vscode
.github
crashtest-0.3.1/.github/ 0000775 0000000 0000000 00000000000 13711015754 0015057 5 ustar 00root root 0000000 0000000 crashtest-0.3.1/.github/workflows/ 0000775 0000000 0000000 00000000000 13711015754 0017114 5 ustar 00root root 0000000 0000000 crashtest-0.3.1/.github/workflows/main.yml 0000664 0000000 0000000 00000004413 13711015754 0020565 0 ustar 00root root 0000000 0000000 name: Main
on:
push:
branches:
- master
tags:
- '**'
pull_request:
branches:
- '**'
jobs:
flake8:
runs-on: ubuntu-latest
steps:
- name: Setup Python
uses: actions/setup-python@v1
with:
python-version: 3.7
architecture: x64
- uses: actions/checkout@master
- name: Install flake8
run: pip install flake8
- name: Run flake8
uses: suo/flake8-github-action@releases/v1
with:
checkName: 'flake8'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run Black
uses: lgeiger/black-action@master
with:
args: --check crashtest/ tests/
tests:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Get full Python version
id: full-python-version
shell: bash
run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
- name: Install poetry
shell: bash
run: |
curl -fsS -o get-poetry.py https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py
python get-poetry.py -y
echo "::set-env name=PATH::$HOME/.poetry/bin:$PATH"
- name: Configure poetry
shell: bash
run: poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v2
id: cache
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }}
- name: Ensure cache is healthy
if: steps.cache.outputs.cache-hit == 'true'
shell: bash
run: poetry run pip --version >/dev/null 2>&1 || rm -rf .venv
- name: Install dependencies
shell: bash
run: poetry install
- name: Run pytest
shell: bash
run: poetry run pytest -v tests
crashtest-0.3.1/.github/workflows/release.yml 0000664 0000000 0000000 00000001630 13711015754 0021257 0 ustar 00root root 0000000 0000000 name: Release
on:
push:
tags:
- '**'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Get tag
id: tag
run: |
echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install dependencies
run: python -m pip install --upgrade pip poetry --pre
- name: Publish release to PyPI
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
run: |
poetry build
poetry publish
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
with:
tag_name: ${{ steps.tag.outputs.tag }}
release_name: ${{ steps.tag.outputs.tag }}
draft: false
prerelease: false
crashtest-0.3.1/.gitignore 0000664 0000000 0000000 00000000366 13711015754 0015514 0 ustar 00root root 0000000 0000000 *.pyc
# Packages
*.egg
*.egg-info
dist
build
.cache
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
.DS_Store
.idea/*
.vscode/*
.python-version
test.py
/test
.pytest_cache
pip-wheel-metadata
setup.py
crashtest-0.3.1/.pre-commit-config.yaml 0000664 0000000 0000000 00000001137 13711015754 0020002 0 ustar 00root root 0000000 0000000 repos:
- repo: https://github.com/psf/black
rev: stable
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.8
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
hooks:
- id: isort
additional_dependencies: [toml]
exclude: ^.*/?setup\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: trailing-whitespace
exclude: ^tests/.*/fixtures/.*
- id: end-of-file-fixer
exclude: ^tests/.*/fixtures/.*
- id: debug-statements
crashtest-0.3.1/LICENSE 0000664 0000000 0000000 00000002046 13711015754 0014526 0 ustar 00root root 0000000 0000000 Copyright (c) 2020 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
crashtest-0.3.1/README.md 0000664 0000000 0000000 00000000141 13711015754 0014772 0 ustar 00root root 0000000 0000000 # Crashtest
Crashtest is a Python library that makes exceptions handling and inspection easier.
crashtest-0.3.1/crashtest/ 0000775 0000000 0000000 00000000000 13711015754 0015517 5 ustar 00root root 0000000 0000000 crashtest-0.3.1/crashtest/__init__.py 0000664 0000000 0000000 00000000026 13711015754 0017626 0 ustar 00root root 0000000 0000000 __version__ = "0.3.1"
crashtest-0.3.1/crashtest/contracts/ 0000775 0000000 0000000 00000000000 13711015754 0017517 5 ustar 00root root 0000000 0000000 crashtest-0.3.1/crashtest/contracts/__init__.py 0000664 0000000 0000000 00000000000 13711015754 0021616 0 ustar 00root root 0000000 0000000 crashtest-0.3.1/crashtest/contracts/base_solution.py 0000664 0000000 0000000 00000001005 13711015754 0022733 0 ustar 00root root 0000000 0000000 from typing import List
from .solution import Solution
class BaseSolution(Solution):
def __init__(self, title: str = None, description: str = None) -> None:
self._title = title
self._description = description
self._links = []
@property
def solution_title(self) -> str:
return self._title
@property
def solution_description(self) -> str:
return self._description
@property
def documentation_links(self) -> List[str]:
return self._links
crashtest-0.3.1/crashtest/contracts/has_solutions_for_exception.py 0000664 0000000 0000000 00000000437 13711015754 0025713 0 ustar 00root root 0000000 0000000 from typing import List
from .solution import Solution
class HasSolutionsForException:
def can_solve(self, exception: Exception) -> bool:
raise NotImplementedError()
def get_solutions(self, exception: Exception) -> List[Solution]:
raise NotImplementedError()
crashtest-0.3.1/crashtest/contracts/provides_solution.py 0000664 0000000 0000000 00000000217 13711015754 0023660 0 ustar 00root root 0000000 0000000 from .solution import Solution
class ProvidesSolution:
@property
def solution(self) -> Solution:
raise NotImplementedError()
crashtest-0.3.1/crashtest/contracts/solution.py 0000664 0000000 0000000 00000000502 13711015754 0021742 0 ustar 00root root 0000000 0000000 from typing import List
class Solution:
@property
def solution_title(self) -> str:
raise NotImplementedError()
@property
def solution_description(self) -> str:
raise NotImplementedError()
@property
def documentation_links(self) -> List[str]:
raise NotImplementedError()
crashtest-0.3.1/crashtest/contracts/solution_provider_repository.py 0000664 0000000 0000000 00000001054 13711015754 0026156 0 ustar 00root root 0000000 0000000 from typing import List
from typing import Type
from .solution import Solution
class SolutionProviderRepository:
def register_solution_provider(
self, solution_provider_class: Type
) -> "SolutionProviderRepository":
raise NotImplementedError()
def register_solution_providers(
self, solution_provider_classes: List[Type]
) -> "SolutionProviderRepository":
raise NotImplementedError()
def get_solutions_for_exception(self, exception: Exception) -> List[Solution]:
raise NotImplementedError()
crashtest-0.3.1/crashtest/frame.py 0000664 0000000 0000000 00000004002 13711015754 0017157 0 ustar 00root root 0000000 0000000 import inspect
from types import FrameType
from typing import Dict
class Frame:
_content_cache: Dict[str, str] = {}
def __init__(self, frame_info: inspect.FrameInfo) -> None:
self._frame = frame_info.frame
self._frame_info = frame_info
self._lineno = frame_info.lineno
self._filename = frame_info.filename
self._function = frame_info.function
self._lines = None
self._file_content = None
@property
def frame(self) -> FrameType:
return self._frame
@property
def lineno(self) -> int:
return self._lineno
@property
def filename(self) -> str:
return self._filename
@property
def function(self) -> str:
return self._function
@property
def line(self) -> str:
if not self._frame_info.code_context:
return ""
return self._frame_info.code_context[0]
@property
def file_content(self) -> str:
if self._file_content is None:
if not self._filename:
file_content = ""
else:
if self._filename not in self.__class__._content_cache:
try:
with open(self._filename) as f:
file_content = f.read()
except OSError:
file_content = ""
self.__class__._content_cache[self._filename] = file_content
file_content = self.__class__._content_cache[self._filename]
self._file_content = file_content
return self._file_content
def __hash__(self) -> int:
return hash(self._filename) ^ hash(self._function) ^ hash(self._lineno)
def __eq__(self, other: "Frame") -> bool:
return (
self._filename == other.filename
and self._function == other.function
and self._lineno == other.lineno
)
def __repr__(self) -> str:
return f""
crashtest-0.3.1/crashtest/frame_collection.py 0000664 0000000 0000000 00000004101 13711015754 0021372 0 ustar 00root root 0000000 0000000 from typing import List
from typing import Optional
from .frame import Frame
class FrameCollection(list):
def __init__(self, frames: Optional[List[Frame]] = None, count: int = 0) -> None:
if frames is None:
frames = []
super().__init__(frames)
self._count = count
@property
def repetitions(self) -> int:
return self._count - 1
def is_repeated(self) -> bool:
return self._count > 1
def increment_count(self, increment: int = 1) -> "FrameCollection":
self._count += increment
return self
def compact(self) -> List["FrameCollection"]:
"""
Compacts the frames to deduplicate recursive calls.
"""
collections = []
current_collection = FrameCollection()
i = 0
while i < len(self) - 1:
frame = self[i]
if frame in self[i + 1 :]:
duplicate_indices = []
for sub_index, sub_frame in enumerate(self[i + 1 :]):
if frame == sub_frame:
duplicate_indices.append(sub_index + i + 1)
found_duplicate = False
for duplicate_index in duplicate_indices:
collection = FrameCollection(self[i:duplicate_index])
if collection == current_collection:
current_collection.increment_count()
i = duplicate_index
found_duplicate = True
break
if found_duplicate:
continue
collections.append(current_collection)
current_collection = FrameCollection(self[i : duplicate_indices[0]])
i = duplicate_indices[0]
continue
if current_collection.is_repeated():
collections.append(current_collection)
current_collection = FrameCollection()
current_collection.append(frame)
i += 1
collections.append(current_collection)
return collections
crashtest-0.3.1/crashtest/inspector.py 0000664 0000000 0000000 00000002377 13711015754 0020110 0 ustar 00root root 0000000 0000000 import inspect
from typing import Optional
from .frame import Frame
from .frame_collection import FrameCollection
class Inspector:
def __init__(self, exception: Exception):
self._exception = exception
self._frames = None
self._outer_frames = None
self._inner_frames = None
self._previous_exception = exception.__context__
@property
def exception(self) -> Exception:
return self._exception
@property
def exception_name(self) -> str:
return self._exception.__class__.__name__
@property
def exception_message(self) -> str:
return str(self._exception)
@property
def frames(self) -> FrameCollection:
if self._frames is not None:
return self._frames
self._frames = FrameCollection()
tb = self._exception.__traceback__
while tb:
frame_info = inspect.getframeinfo(tb)
self._frames.append(Frame(inspect.FrameInfo(tb.tb_frame, *frame_info)))
tb = tb.tb_next
return self._frames
@property
def previous_exception(self) -> Optional[Exception]:
return self._previous_exception
def has_previous_exception(self) -> bool:
return self._previous_exception is not None
crashtest-0.3.1/crashtest/solution_providers/ 0000775 0000000 0000000 00000000000 13711015754 0021470 5 ustar 00root root 0000000 0000000 crashtest-0.3.1/crashtest/solution_providers/__init__.py 0000664 0000000 0000000 00000000000 13711015754 0023567 0 ustar 00root root 0000000 0000000 crashtest-0.3.1/crashtest/solution_providers/solution_provider_repository.py 0000664 0000000 0000000 00000004165 13711015754 0030135 0 ustar 00root root 0000000 0000000 from typing import List
from typing import Optional
from typing import Type
from crashtest.contracts.has_solutions_for_exception import HasSolutionsForException
from crashtest.contracts.provides_solution import ProvidesSolution
from crashtest.contracts.solution import Solution
from crashtest.contracts.solution_provider_repository import (
SolutionProviderRepository as BaseSolutionProviderRepository,
)
class SolutionProviderRepository(BaseSolutionProviderRepository):
def __init__(self, solution_providers: Optional[List[Type]] = None) -> None:
self._solution_providers = []
if solution_providers is None:
solution_providers = []
self.register_solution_providers(solution_providers)
def register_solution_provider(
self, solution_provider_class: Type
) -> "SolutionProviderRepository":
self._solution_providers.append(solution_provider_class)
return self
def register_solution_providers(
self, solution_provider_classes: List[Type]
) -> "SolutionProviderRepository":
for solution_provider_class in solution_provider_classes:
self.register_solution_provider(solution_provider_class)
return self
def get_solutions_for_exception(self, exception: Exception) -> List[Solution]:
solutions = []
if isinstance(exception, Solution):
solutions.append(exception)
if isinstance(exception, ProvidesSolution):
solutions.append(exception.solution)
for solution_provider_class in self._solution_providers:
if not issubclass(solution_provider_class, HasSolutionsForException):
continue
solution_provider: HasSolutionsForException = solution_provider_class()
try:
if not solution_provider.can_solve(exception):
continue
except Exception:
continue
try:
for solution in solution_provider.get_solutions(exception):
solutions.append(solution)
except Exception:
continue
return solutions
crashtest-0.3.1/poetry.lock 0000664 0000000 0000000 00000067674 13711015754 0015737 0 ustar 00root root 0000000 0000000 [[package]]
category = "dev"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
name = "appdirs"
optional = false
python-versions = "*"
version = "1.4.3"
[[package]]
category = "dev"
description = "A few extensions to pyyaml."
name = "aspy.yaml"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.3.0"
[package.dependencies]
pyyaml = "*"
[[package]]
category = "dev"
description = "Atomic file writes."
marker = "sys_platform == \"win32\""
name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.3.0"
[[package]]
category = "dev"
description = "Classes Without Boilerplate"
name = "attrs"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.3.0"
[package.extras]
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
docs = ["sphinx", "zope.interface"]
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
[[package]]
category = "dev"
description = "The uncompromising code formatter."
name = "black"
optional = false
python-versions = ">=3.6"
version = "19.10b0"
[package.dependencies]
appdirs = "*"
attrs = ">=18.1.0"
click = ">=6.5"
pathspec = ">=0.6,<1"
regex = "*"
toml = ">=0.9.4"
typed-ast = ">=1.4.0"
[package.extras]
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
[[package]]
category = "dev"
description = "Validate configuration and produce human readable error messages."
name = "cfgv"
optional = false
python-versions = ">=3.6"
version = "3.0.0"
[[package]]
category = "dev"
description = "Composable command line interface toolkit"
name = "click"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "7.1.1"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.3"
[[package]]
category = "dev"
description = "Code coverage measurement for Python"
name = "coverage"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "5.0.4"
[package.extras]
toml = ["toml"]
[[package]]
category = "dev"
description = "Distribution utilities"
name = "distlib"
optional = false
python-versions = "*"
version = "0.3.0"
[[package]]
category = "dev"
description = "A platform independent file lock."
name = "filelock"
optional = false
python-versions = "*"
version = "3.0.12"
[[package]]
category = "dev"
description = "File identification library for Python"
name = "identify"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "1.4.13"
[package.extras]
license = ["editdistance"]
[[package]]
category = "dev"
description = "Read metadata from Python packages"
marker = "python_version < \"3.8\""
name = "importlib-metadata"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.6.0"
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "rst.linker"]
testing = ["packaging", "importlib-resources"]
[[package]]
category = "dev"
description = "Read resources from Python packages"
marker = "python_version < \"3.7\""
name = "importlib-resources"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.4.0"
[package.dependencies]
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
[package.dependencies.zipp]
python = "<3.8"
version = ">=0.4"
[package.extras]
docs = ["sphinx", "rst.linker", "jaraco.packaging"]
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
python-versions = ">=3.5"
version = "8.2.0"
[[package]]
category = "dev"
description = "Node.js virtual environment builder"
name = "nodeenv"
optional = false
python-versions = "*"
version = "1.3.5"
[[package]]
category = "dev"
description = "Core utilities for Python packages"
name = "packaging"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "20.3"
[package.dependencies]
pyparsing = ">=2.0.2"
six = "*"
[[package]]
category = "dev"
description = "Utility library for gitignore style pattern matching of file paths."
name = "pathspec"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.7.0"
[[package]]
category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.1"
[package.dependencies]
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
category = "dev"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
name = "pre-commit"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.21.0"
[package.dependencies]
"aspy.yaml" = "*"
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = "*"
six = "*"
toml = "*"
virtualenv = ">=15.2"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
[package.dependencies.importlib-resources]
python = "<3.7"
version = "*"
[[package]]
category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.8.1"
[[package]]
category = "dev"
description = "Python parsing module"
name = "pyparsing"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.6"
[[package]]
category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=3.5"
version = "5.4.1"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras]
checkqa-mypy = ["mypy (v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
category = "dev"
description = "Pytest plugin for measuring coverage."
name = "pytest-cov"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.8.1"
[package.dependencies]
coverage = ">=4.4"
pytest = ">=3.6"
[package.extras]
testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"]
[[package]]
category = "dev"
description = "YAML parser and emitter for Python"
name = "pyyaml"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "5.3.1"
[[package]]
category = "dev"
description = "Alternative regular expression module, to replace re."
name = "regex"
optional = false
python-versions = "*"
version = "2020.2.20"
[[package]]
category = "dev"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.14.0"
[[package]]
category = "dev"
description = "Python Library for Tom's Obvious, Minimal Language"
name = "toml"
optional = false
python-versions = "*"
version = "0.10.0"
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
name = "typed-ast"
optional = false
python-versions = "*"
version = "1.4.1"
[[package]]
category = "dev"
description = "Virtual Python Environment builder"
name = "virtualenv"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "20.0.15"
[package.dependencies]
appdirs = ">=1.4.3,<2"
distlib = ">=0.3.0,<1"
filelock = ">=3.0.0,<4"
six = ">=1.9.0,<2"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12,<2"
[package.dependencies.importlib-resources]
python = "<3.7"
version = ">=1.0,<2"
[package.extras]
docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"]
testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "pytest-timeout (>=1.3.4,<2)", "packaging (>=20.0)", "xonsh (>=0.9.13,<1)"]
[[package]]
category = "dev"
description = "Measures number of Terminal column cells of wide-character codes"
name = "wcwidth"
optional = false
python-versions = "*"
version = "0.1.9"
[[package]]
category = "dev"
description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version < \"3.8\""
name = "zipp"
optional = false
python-versions = ">=3.6"
version = "3.1.0"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata]
content-hash = "f308842e9da34e25902342e2613bf67e30f96591bda6d639f88808f7a0e6880e"
python-versions = "^3.6"
[metadata.files]
appdirs = [
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
]
"aspy.yaml" = [
{file = "aspy.yaml-1.3.0-py2.py3-none-any.whl", hash = "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc"},
{file = "aspy.yaml-1.3.0.tar.gz", hash = "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"},
]
atomicwrites = [
{file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"},
{file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"},
]
attrs = [
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
]
black = [
{file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
{file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
]
cfgv = [
{file = "cfgv-3.0.0-py2.py3-none-any.whl", hash = "sha256:f22b426ed59cd2ab2b54ff96608d846c33dfb8766a67f0b4a6ce130ce244414f"},
{file = "cfgv-3.0.0.tar.gz", hash = "sha256:04b093b14ddf9fd4d17c53ebfd55582d27b76ed30050193c14e560770c5360eb"},
]
click = [
{file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"},
{file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"},
]
colorama = [
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
]
coverage = [
{file = "coverage-5.0.4-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307"},
{file = "coverage-5.0.4-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8"},
{file = "coverage-5.0.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31"},
{file = "coverage-5.0.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441"},
{file = "coverage-5.0.4-cp27-cp27m-win32.whl", hash = "sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac"},
{file = "coverage-5.0.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435"},
{file = "coverage-5.0.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037"},
{file = "coverage-5.0.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a"},
{file = "coverage-5.0.4-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5"},
{file = "coverage-5.0.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30"},
{file = "coverage-5.0.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7"},
{file = "coverage-5.0.4-cp35-cp35m-win32.whl", hash = "sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de"},
{file = "coverage-5.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1"},
{file = "coverage-5.0.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1"},
{file = "coverage-5.0.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0"},
{file = "coverage-5.0.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd"},
{file = "coverage-5.0.4-cp36-cp36m-win32.whl", hash = "sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0"},
{file = "coverage-5.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b"},
{file = "coverage-5.0.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78"},
{file = "coverage-5.0.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6"},
{file = "coverage-5.0.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014"},
{file = "coverage-5.0.4-cp37-cp37m-win32.whl", hash = "sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732"},
{file = "coverage-5.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006"},
{file = "coverage-5.0.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2"},
{file = "coverage-5.0.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe"},
{file = "coverage-5.0.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9"},
{file = "coverage-5.0.4-cp38-cp38-win32.whl", hash = "sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1"},
{file = "coverage-5.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0"},
{file = "coverage-5.0.4-cp39-cp39-win32.whl", hash = "sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7"},
{file = "coverage-5.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892"},
{file = "coverage-5.0.4.tar.gz", hash = "sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823"},
]
distlib = [
{file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"},
]
filelock = [
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
]
identify = [
{file = "identify-1.4.13-py2.py3-none-any.whl", hash = "sha256:a7577a1f55cee1d21953a5cf11a3c839ab87f5ef909a4cba6cf52ed72b4c6059"},
{file = "identify-1.4.13.tar.gz", hash = "sha256:ab246293e6585a1c6361a505b68d5b501a0409310932b7de2c2ead667b564d89"},
]
importlib-metadata = [
{file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"},
{file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"},
]
importlib-resources = [
{file = "importlib_resources-1.4.0-py2.py3-none-any.whl", hash = "sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8"},
{file = "importlib_resources-1.4.0.tar.gz", hash = "sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2"},
]
more-itertools = [
{file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"},
{file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"},
]
nodeenv = [
{file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"},
]
packaging = [
{file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"},
{file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"},
]
pathspec = [
{file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"},
{file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"},
]
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-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"},
{file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"},
]
py = [
{file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"},
{file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"},
]
pyparsing = [
{file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
{file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
]
pytest = [
{file = "pytest-5.4.1-py3-none-any.whl", hash = "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172"},
{file = "pytest-5.4.1.tar.gz", hash = "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"},
]
pytest-cov = [
{file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"},
{file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"},
]
pyyaml = [
{file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
{file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},
{file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"},
{file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"},
{file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"},
{file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"},
{file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"},
{file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
{file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
{file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
{file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
]
regex = [
{file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"},
{file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"},
{file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"},
{file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"},
{file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"},
{file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"},
{file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"},
{file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"},
{file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"},
{file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"},
{file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"},
{file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"},
{file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"},
{file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"},
{file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"},
{file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"},
{file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"},
{file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"},
{file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"},
{file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"},
{file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"},
]
six = [
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
]
toml = [
{file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
{file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
{file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
]
typed-ast = [
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
{file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
{file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
]
virtualenv = [
{file = "virtualenv-20.0.15-py2.py3-none-any.whl", hash = "sha256:4e399f48c6b71228bf79f5febd27e3bbb753d9d5905776a86667bc61ab628a25"},
{file = "virtualenv-20.0.15.tar.gz", hash = "sha256:9e81279f4a9d16d1c0654a127c2c86e5bca2073585341691882c1e66e31ef8a5"},
]
wcwidth = [
{file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"},
{file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"},
]
zipp = [
{file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
{file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
]
crashtest-0.3.1/pyproject.toml 0000664 0000000 0000000 00000001556 13711015754 0016442 0 ustar 00root root 0000000 0000000 [tool.poetry]
name = "crashtest"
version = "0.3.1"
description = "Manage Python errors with ease"
authors = ["Sébastien Eustace "]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/sdispater/crashtest"
repository = "https://github.com/sdispater/crashtest"
[tool.poetry.dependencies]
python = "^3.6"
[tool.poetry.dev-dependencies]
pytest = "^5.4.1"
black = "^19.10b0"
pytest-cov = "^2.8.1"
pre-commit = "^1.20.0"
[tool.isort]
line_length = 88
force_single_line = true
atomic = true
include_trailing_comma = true
lines_after_imports = 2
lines_between_types = 1
multi_line_output = 3
use_parentheses = true
not_skip = "__init__.py"
skip_glob = ["*/setup.py"]
filter_files = true
known_first_party = "crashtest"
known_third_party = ["pytest"]
[build-system]
requires = ["poetry-core>=1.0.0a5"]
build-backend = "poetry.core.masonry.api"
crashtest-0.3.1/tests/ 0000775 0000000 0000000 00000000000 13711015754 0014661 5 ustar 00root root 0000000 0000000 crashtest-0.3.1/tests/__init__.py 0000664 0000000 0000000 00000000000 13711015754 0016760 0 ustar 00root root 0000000 0000000 crashtest-0.3.1/tests/helpers.py 0000664 0000000 0000000 00000000462 13711015754 0016677 0 ustar 00root root 0000000 0000000 def simple_exception():
raise ValueError("Simple Exception")
def nested_exception():
try:
simple_exception()
except ValueError:
raise RuntimeError("Nested Exception")
def recursive_exception():
def inner():
outer()
def outer():
inner()
inner()
crashtest-0.3.1/tests/solution_providers/ 0000775 0000000 0000000 00000000000 13711015754 0020632 5 ustar 00root root 0000000 0000000 crashtest-0.3.1/tests/solution_providers/__init__.py 0000664 0000000 0000000 00000000000 13711015754 0022731 0 ustar 00root root 0000000 0000000 crashtest-0.3.1/tests/solution_providers/test_solution_provider_repository.py 0000664 0000000 0000000 00000005260 13711015754 0030333 0 ustar 00root root 0000000 0000000 from typing import List
from crashtest.contracts.base_solution import BaseSolution
from crashtest.contracts.has_solutions_for_exception import HasSolutionsForException
from crashtest.contracts.provides_solution import ProvidesSolution
from crashtest.contracts.solution import Solution
from crashtest.solution_providers.solution_provider_repository import (
SolutionProviderRepository,
)
class ExceptionSolutionProvider(HasSolutionsForException):
def can_solve(self, exception: Exception) -> bool:
return isinstance(exception, ExceptionProvidingException)
def get_solutions(self, exception: Exception) -> List[Solution]:
return [
BaseSolution("An exception solution", "An exception solution description")
]
class ExceptionProvidingException(Exception, ProvidesSolution):
@property
def solution(self) -> Solution:
solution = BaseSolution("A simple solution", "A simple solution description")
solution.documentation_links.append("https://example.com")
return solution
class ExceptionWhichIsSolution(Exception, Solution):
@property
def solution_title(self) -> str:
return "A solution"
@property
def solution_description(self) -> str:
return "A solution description"
@property
def documentation_links(self) -> List[str]:
return ["https://foo.bar"]
def test_it_has_no_provider_by_default():
repository = SolutionProviderRepository()
assert 0 == len(repository._solution_providers)
def test_providers_can_be_passed_to_constructor():
repository = SolutionProviderRepository()
assert 0 == len(repository._solution_providers)
def test_it_can_find_solutions():
repository = SolutionProviderRepository()
repository.register_solution_provider(ExceptionSolutionProvider)
solutions = repository.get_solutions_for_exception(ExceptionProvidingException())
assert 2 == len(solutions)
solution1 = solutions[0]
solution2 = solutions[1]
assert "A simple solution" == solution1.solution_title
assert "An exception solution" == solution2.solution_title
assert "A simple solution description" == solution1.solution_description
assert "An exception solution description" == solution2.solution_description
assert ["https://example.com"] == solution1.documentation_links
assert 0 == len(solution2.documentation_links)
solutions = repository.get_solutions_for_exception(ExceptionWhichIsSolution())
assert 1 == len(solutions)
solution1 = solutions[0]
assert "A solution" == solution1.solution_title
assert "A solution description" == solution1.solution_description
assert ["https://foo.bar"] == solution1.documentation_links
crashtest-0.3.1/tests/test_frame.py 0000664 0000000 0000000 00000002255 13711015754 0017370 0 ustar 00root root 0000000 0000000 import inspect
from crashtest.frame import Frame
from .helpers import nested_exception
from .helpers import simple_exception
def test_frame():
try:
simple_exception()
except ValueError as e:
frame_info = inspect.getinnerframes(e.__traceback__)[0]
frame = Frame(frame_info)
same_frame = Frame(frame_info)
assert frame_info.frame == frame.frame
assert 11 == frame.lineno
assert __file__ == frame.filename
assert "test_frame" == frame.function
assert " simple_exception()\n" == frame.line
with open(__file__) as f:
assert f.read() == frame.file_content
assert f"" == repr(frame)
try:
nested_exception()
except Exception as e:
frame_info = inspect.getinnerframes(e.__traceback__)[0]
other_frame = Frame(frame_info)
assert same_frame == frame
assert other_frame != frame
assert hash(same_frame) == hash(frame)
assert hash(other_frame) != hash(frame)
def test_frame_with_no_context_should_return_empty_line():
frame = Frame(inspect.FrameInfo(None, "filename.py", 123, "function", None, 3))
assert "" == frame.line
crashtest-0.3.1/tests/test_inspector.py 0000664 0000000 0000000 00000003207 13711015754 0020302 0 ustar 00root root 0000000 0000000 from crashtest.inspector import Inspector
from .helpers import nested_exception
from .helpers import recursive_exception
from .helpers import simple_exception
def test_inspector_with_simple_exception():
try:
simple_exception()
except ValueError as e:
inspector = Inspector(e)
assert e == inspector.exception
assert not inspector.has_previous_exception()
assert inspector.previous_exception is None
assert "ValueError" == inspector.exception_name
assert "Simple Exception" == inspector.exception_message
assert len(inspector.frames) > 0
def test_inspector_with_nested_exception():
try:
nested_exception()
except RuntimeError as e:
inspector = Inspector(e)
assert e == inspector.exception
assert inspector.has_previous_exception()
assert inspector.previous_exception is not None
assert "RuntimeError" == inspector.exception_name
assert "Nested Exception" == inspector.exception_message
assert len(inspector.frames) > 0
assert 1 == len(inspector.frames.compact())
def test_inspector_with_recursive_exception():
try:
recursive_exception()
except RuntimeError as e:
inspector = Inspector(e)
assert e == inspector.exception
assert not inspector.has_previous_exception()
assert inspector.previous_exception is None
assert "RecursionError" == inspector.exception_name
assert "maximum recursion depth exceeded" == inspector.exception_message
assert len(inspector.frames) > 0
assert len(inspector.frames) > len(inspector.frames.compact())