├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── README.md ├── poetry.lock ├── pyproject.toml ├── pytest_markdown ├── __init__.py └── plugin.py ├── scripts ├── release.sh └── test.sh ├── setup.cfg └── tests └── integration └── example.md /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: ~ 8 | 9 | jobs: 10 | lint: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Check out code from GitHub 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v1 20 | with: 21 | python-version: 3.7 22 | 23 | - name: Get full Python version 24 | id: full-python-version 25 | shell: bash 26 | run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") 27 | 28 | - name: Install poetry 29 | shell: bash 30 | run: | 31 | python -m pip install -U pip poetry 32 | echo "$HOME/.poetry/bin:$PATH" >> $GITHUB_PATH 33 | 34 | - name: Configure poetry 35 | shell: bash 36 | run: poetry config virtualenvs.in-project true 37 | 38 | - name: Set up cache 39 | uses: actions/cache@v2 40 | id: cache 41 | with: 42 | path: .venv 43 | key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} 44 | 45 | - name: Ensure cache is healthy 46 | if: steps.cache.outputs.cache-hit == 'true' 47 | shell: bash 48 | run: poetry run pip --version >/dev/null 2>&1 || rm -rf .venv 49 | 50 | - name: Install dependencies 51 | shell: bash 52 | run: poetry install 53 | 54 | - name: Run pyupgrade 55 | shell: bash 56 | run: poetry run find pytest_markdown tests -name '*.py' -exec python -m pyupgrade --py37-plus {} + && git diff --exit-code 57 | 58 | - name: Run isort 59 | shell: bash 60 | run: poetry run python -m isort -c --diff pytest_markdown tests 61 | 62 | - name: Run black 63 | shell: bash 64 | run: poetry run python -m black --check --diff pytest_markdown tests 65 | 66 | - name: Run flake8 67 | shell: bash 68 | run: poetry run python -m flake8 pytest_markdown tests 69 | 70 | - name: Run poetry build 71 | shell: bash 72 | run: poetry build 73 | 74 | - name: Run poetry check 75 | shell: bash 76 | run: poetry check 77 | 78 | tests: 79 | name: pytest/${{ matrix.os }}/${{ matrix.python-version }} 80 | runs-on: ${{ matrix.os }}-latest 81 | 82 | strategy: 83 | matrix: 84 | os: [Ubuntu, MacOS, Windows] 85 | python-version: [3.7, 3.8, 3.9] 86 | 87 | env: 88 | OS: ${{ matrix.os }} 89 | PYTHON: ${{ matrix.python-version }} 90 | 91 | steps: 92 | - name: Check out code from GitHub 93 | uses: actions/checkout@v2 94 | 95 | - name: Set up Python ${{ matrix.python-version }} 96 | uses: actions/setup-python@v1 97 | with: 98 | python-version: ${{ matrix.python-version }} 99 | 100 | - name: Get full Python version 101 | id: full-python-version 102 | shell: bash 103 | run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") 104 | 105 | - name: Install poetry 106 | shell: bash 107 | run: | 108 | python -m pip install -U pip poetry 109 | echo "$HOME/.poetry/bin:$PATH" >> $GITHUB_PATH 110 | 111 | - name: Configure poetry 112 | shell: bash 113 | run: poetry config virtualenvs.in-project true 114 | 115 | - name: Set up cache 116 | uses: actions/cache@v2 117 | id: cache 118 | with: 119 | path: .venv 120 | key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} 121 | 122 | - name: Ensure cache is healthy 123 | if: steps.cache.outputs.cache-hit == 'true' 124 | shell: bash 125 | run: poetry run pip --version >/dev/null 2>&1 || rm -rf .venv 126 | 127 | - name: Install dependencies 128 | shell: bash 129 | run: poetry install 130 | 131 | - name: Run pytest 132 | shell: bash 133 | run: poetry run coverage run -m pytest 134 | 135 | - name: Upload coverage to Codecov 136 | shell: bash 137 | run: poetry run codecov --flags unittests -e OS -e PYTHON --tries 20 138 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Release 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*.*.*' 8 | 9 | jobs: 10 | Release: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Get tag 18 | id: tag 19 | run: | 20 | echo ::set-output name=tag::${GITHUB_REF#refs/tags/} 21 | 22 | - name: Set up Python 3.7 23 | uses: actions/setup-python@v1 24 | with: 25 | python-version: 3.7 26 | 27 | - name: Get full Python version 28 | id: full-python-version 29 | shell: bash 30 | run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") 31 | 32 | - name: Install poetry 33 | shell: bash 34 | run: | 35 | python -m pip install -U pip poetry 36 | echo "$HOME/.poetry/bin:$PATH" >> $GITHUB_PATH 37 | 38 | - name: Configure poetry 39 | shell: bash 40 | run: poetry config virtualenvs.in-project true 41 | 42 | - name: Set up cache 43 | uses: actions/cache@v2 44 | id: cache 45 | with: 46 | path: .venv 47 | key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} 48 | 49 | - name: Ensure cache is healthy 50 | if: steps.cache.outputs.cache-hit == 'true' 51 | shell: bash 52 | run: poetry run pip --version >/dev/null 2>&1 || rm -rf .venv 53 | 54 | - name: Install dependencies 55 | shell: bash 56 | run: poetry install 57 | 58 | - name: Build project for distribution 59 | run: poetry run poetry build 60 | 61 | - name: Publish to PyPI 62 | env: 63 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} 64 | run: poetry run poetry publish 65 | 66 | - name: Create Release 67 | id: create_release 68 | uses: actions/create-release@v1 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | with: 72 | tag_name: ${{ steps.tag.outputs.tag }} 73 | release_name: ${{ steps.tag.outputs.tag }} 74 | draft: false 75 | prerelease: false 76 | 77 | - name: Upload Linux release file asset 78 | uses: actions/upload-release-asset@v1.0.1 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | with: 82 | upload_url: ${{ steps.create_release.outputs.upload_url }} 83 | asset_path: dist/pytest_markdown-${{ steps.tag.outputs.tag }}.tar.gz 84 | asset_name: pytest_markdown-${{ steps.tag.outputs.tag }}.tar.gz 85 | asset_content_type: application/gzip 86 | 87 | - name: Upload Linux checksum file asset 88 | uses: actions/upload-release-asset@v1.0.1 89 | env: 90 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 91 | with: 92 | upload_url: ${{ steps.create_release.outputs.upload_url }} 93 | asset_path: dist/pytest_markdown-${{ steps.tag.outputs.tag }}-py3-none-any.whl 94 | asset_name: pytest_markdown-${{ steps.tag.outputs.tag }}-py3-none-any.whl 95 | asset_content_type: application/x-wheel+zip 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .pytest_cache/ 3 | pytest_markdown.egg-info 4 | *.pyc 5 | venv 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytest-markdown 2 | 3 | You have written a `README.md`. In contains some of your best words. They are in an order, and you are happy its a good order. But all those code blocks... Do they contain valid python? This plugin will find tests in your markdown files and run them. 4 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "dev" 3 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 4 | name = "appdirs" 5 | optional = false 6 | python-versions = "*" 7 | version = "1.4.4" 8 | 9 | [[package]] 10 | category = "main" 11 | description = "Atomic file writes." 12 | marker = "sys_platform == \"win32\"" 13 | name = "atomicwrites" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 16 | version = "1.4.0" 17 | 18 | [[package]] 19 | category = "main" 20 | description = "Classes Without Boilerplate" 21 | name = "attrs" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | version = "20.3.0" 25 | 26 | [package.extras] 27 | dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 28 | docs = ["furo", "sphinx", "zope.interface"] 29 | tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 30 | tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 31 | 32 | [[package]] 33 | category = "dev" 34 | description = "The uncompromising code formatter." 35 | name = "black" 36 | optional = false 37 | python-versions = ">=3.6" 38 | version = "20.8b1" 39 | 40 | [package.dependencies] 41 | appdirs = "*" 42 | click = ">=7.1.2" 43 | mypy-extensions = ">=0.4.3" 44 | pathspec = ">=0.6,<1" 45 | regex = ">=2020.1.8" 46 | toml = ">=0.10.1" 47 | typed-ast = ">=1.4.0" 48 | typing-extensions = ">=3.7.4" 49 | 50 | [package.dependencies.dataclasses] 51 | python = "<3.7" 52 | version = ">=0.6" 53 | 54 | [package.extras] 55 | colorama = ["colorama (>=0.4.3)"] 56 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 57 | 58 | [[package]] 59 | category = "dev" 60 | description = "Python package for providing Mozilla's CA Bundle." 61 | name = "certifi" 62 | optional = false 63 | python-versions = "*" 64 | version = "2020.12.5" 65 | 66 | [[package]] 67 | category = "dev" 68 | description = "Universal encoding detector for Python 2 and 3" 69 | name = "chardet" 70 | optional = false 71 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 72 | version = "4.0.0" 73 | 74 | [[package]] 75 | category = "dev" 76 | description = "Composable command line interface toolkit" 77 | name = "click" 78 | optional = false 79 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 80 | version = "7.1.2" 81 | 82 | [[package]] 83 | category = "dev" 84 | description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" 85 | name = "codecov" 86 | optional = false 87 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 88 | version = "2.1.11" 89 | 90 | [package.dependencies] 91 | coverage = "*" 92 | requests = ">=2.7.9" 93 | 94 | [[package]] 95 | category = "main" 96 | description = "Cross-platform colored terminal text." 97 | marker = "sys_platform == \"win32\"" 98 | name = "colorama" 99 | optional = false 100 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 101 | version = "0.4.4" 102 | 103 | [[package]] 104 | category = "main" 105 | description = "Python parser for the CommonMark Markdown spec" 106 | name = "commonmark" 107 | optional = false 108 | python-versions = "*" 109 | version = "0.9.1" 110 | 111 | [package.extras] 112 | test = ["flake8 (3.7.8)", "hypothesis (3.55.3)"] 113 | 114 | [[package]] 115 | category = "dev" 116 | description = "Code coverage measurement for Python" 117 | name = "coverage" 118 | optional = false 119 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 120 | version = "5.3.1" 121 | 122 | [package.extras] 123 | toml = ["toml"] 124 | 125 | [[package]] 126 | category = "dev" 127 | description = "A backport of the dataclasses module for Python 3.6" 128 | marker = "python_version < \"3.7\"" 129 | name = "dataclasses" 130 | optional = false 131 | python-versions = "*" 132 | version = "0.6" 133 | 134 | [[package]] 135 | category = "dev" 136 | description = "the modular source code checker: pep8 pyflakes and co" 137 | name = "flake8" 138 | optional = false 139 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 140 | version = "3.8.4" 141 | 142 | [package.dependencies] 143 | mccabe = ">=0.6.0,<0.7.0" 144 | pycodestyle = ">=2.6.0a1,<2.7.0" 145 | pyflakes = ">=2.2.0,<2.3.0" 146 | 147 | [package.dependencies.importlib-metadata] 148 | python = "<3.8" 149 | version = "*" 150 | 151 | [[package]] 152 | category = "dev" 153 | description = "Internationalized Domain Names in Applications (IDNA)" 154 | name = "idna" 155 | optional = false 156 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 157 | version = "2.10" 158 | 159 | [[package]] 160 | category = "main" 161 | description = "Read metadata from Python packages" 162 | marker = "python_version < \"3.8\"" 163 | name = "importlib-metadata" 164 | optional = false 165 | python-versions = ">=3.6" 166 | version = "3.4.0" 167 | 168 | [package.dependencies] 169 | zipp = ">=0.5" 170 | 171 | [package.dependencies.typing-extensions] 172 | python = "<3.8" 173 | version = ">=3.6.4" 174 | 175 | [package.extras] 176 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 177 | testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 178 | 179 | [[package]] 180 | category = "main" 181 | description = "iniconfig: brain-dead simple config-ini parsing" 182 | name = "iniconfig" 183 | optional = false 184 | python-versions = "*" 185 | version = "1.1.1" 186 | 187 | [[package]] 188 | category = "dev" 189 | description = "A Python utility / library to sort Python imports." 190 | name = "isort" 191 | optional = false 192 | python-versions = ">=3.6,<4.0" 193 | version = "5.7.0" 194 | 195 | [package.extras] 196 | colors = ["colorama (>=0.4.3,<0.5.0)"] 197 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 198 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 199 | 200 | [[package]] 201 | category = "dev" 202 | description = "McCabe checker, plugin for flake8" 203 | name = "mccabe" 204 | optional = false 205 | python-versions = "*" 206 | version = "0.6.1" 207 | 208 | [[package]] 209 | category = "dev" 210 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 211 | name = "mypy-extensions" 212 | optional = false 213 | python-versions = "*" 214 | version = "0.4.3" 215 | 216 | [[package]] 217 | category = "main" 218 | description = "Core utilities for Python packages" 219 | name = "packaging" 220 | optional = false 221 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 222 | version = "20.8" 223 | 224 | [package.dependencies] 225 | pyparsing = ">=2.0.2" 226 | 227 | [[package]] 228 | category = "dev" 229 | description = "Utility library for gitignore style pattern matching of file paths." 230 | name = "pathspec" 231 | optional = false 232 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 233 | version = "0.8.1" 234 | 235 | [[package]] 236 | category = "main" 237 | description = "plugin and hook calling mechanisms for python" 238 | name = "pluggy" 239 | optional = false 240 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 241 | version = "0.13.1" 242 | 243 | [package.dependencies] 244 | [package.dependencies.importlib-metadata] 245 | python = "<3.8" 246 | version = ">=0.12" 247 | 248 | [package.extras] 249 | dev = ["pre-commit", "tox"] 250 | 251 | [[package]] 252 | category = "main" 253 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 254 | name = "py" 255 | optional = false 256 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 257 | version = "1.10.0" 258 | 259 | [[package]] 260 | category = "dev" 261 | description = "Python style guide checker" 262 | name = "pycodestyle" 263 | optional = false 264 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 265 | version = "2.6.0" 266 | 267 | [[package]] 268 | category = "dev" 269 | description = "passive checker of Python programs" 270 | name = "pyflakes" 271 | optional = false 272 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 273 | version = "2.2.0" 274 | 275 | [[package]] 276 | category = "main" 277 | description = "Python parsing module" 278 | name = "pyparsing" 279 | optional = false 280 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 281 | version = "2.4.7" 282 | 283 | [[package]] 284 | category = "main" 285 | description = "pytest: simple powerful testing with Python" 286 | name = "pytest" 287 | optional = false 288 | python-versions = ">=3.6" 289 | version = "6.2.1" 290 | 291 | [package.dependencies] 292 | atomicwrites = ">=1.0" 293 | attrs = ">=19.2.0" 294 | colorama = "*" 295 | iniconfig = "*" 296 | packaging = "*" 297 | pluggy = ">=0.12,<1.0.0a1" 298 | py = ">=1.8.2" 299 | toml = "*" 300 | 301 | [package.dependencies.importlib-metadata] 302 | python = "<3.8" 303 | version = ">=0.12" 304 | 305 | [package.extras] 306 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 307 | 308 | [[package]] 309 | category = "dev" 310 | description = "Pytest plugin for measuring coverage." 311 | name = "pytest-cov" 312 | optional = false 313 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 314 | version = "2.10.1" 315 | 316 | [package.dependencies] 317 | coverage = ">=4.4" 318 | pytest = ">=4.6" 319 | 320 | [package.extras] 321 | testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] 322 | 323 | [[package]] 324 | category = "dev" 325 | description = "A tool to automatically upgrade syntax for newer versions." 326 | name = "pyupgrade" 327 | optional = false 328 | python-versions = ">=3.6.1" 329 | version = "2.7.4" 330 | 331 | [package.dependencies] 332 | tokenize-rt = ">=3.2.0" 333 | 334 | [[package]] 335 | category = "dev" 336 | description = "Alternative regular expression module, to replace re." 337 | name = "regex" 338 | optional = false 339 | python-versions = "*" 340 | version = "2020.11.13" 341 | 342 | [[package]] 343 | category = "dev" 344 | description = "Python HTTP for Humans." 345 | name = "requests" 346 | optional = false 347 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 348 | version = "2.25.1" 349 | 350 | [package.dependencies] 351 | certifi = ">=2017.4.17" 352 | chardet = ">=3.0.2,<5" 353 | idna = ">=2.5,<3" 354 | urllib3 = ">=1.21.1,<1.27" 355 | 356 | [package.extras] 357 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 358 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] 359 | 360 | [[package]] 361 | category = "dev" 362 | description = "A wrapper around the stdlib `tokenize` which roundtrips." 363 | name = "tokenize-rt" 364 | optional = false 365 | python-versions = ">=3.6.1" 366 | version = "4.0.0" 367 | 368 | [[package]] 369 | category = "main" 370 | description = "Python Library for Tom's Obvious, Minimal Language" 371 | name = "toml" 372 | optional = false 373 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 374 | version = "0.10.2" 375 | 376 | [[package]] 377 | category = "dev" 378 | description = "a fork of Python 2 and 3 ast modules with type comment support" 379 | name = "typed-ast" 380 | optional = false 381 | python-versions = "*" 382 | version = "1.4.2" 383 | 384 | [[package]] 385 | category = "main" 386 | description = "Backported and Experimental Type Hints for Python 3.5+" 387 | name = "typing-extensions" 388 | optional = false 389 | python-versions = "*" 390 | version = "3.7.4.3" 391 | 392 | [[package]] 393 | category = "dev" 394 | description = "HTTP library with thread-safe connection pooling, file post, and more." 395 | name = "urllib3" 396 | optional = false 397 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 398 | version = "1.26.2" 399 | 400 | [package.extras] 401 | brotli = ["brotlipy (>=0.6.0)"] 402 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 403 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] 404 | 405 | [[package]] 406 | category = "main" 407 | description = "Backport of pathlib-compatible object wrapper for zip files" 408 | marker = "python_version < \"3.8\"" 409 | name = "zipp" 410 | optional = false 411 | python-versions = ">=3.6" 412 | version = "3.4.0" 413 | 414 | [package.extras] 415 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 416 | testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 417 | 418 | [metadata] 419 | content-hash = "18e7088ea6cfde7ed5cb9b461a023e167dd29a0ae4f2e5ef97cc924a7e96faec" 420 | lock-version = "1.0" 421 | python-versions = "^3.6.1" 422 | 423 | [metadata.files] 424 | appdirs = [ 425 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 426 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 427 | ] 428 | atomicwrites = [ 429 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 430 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 431 | ] 432 | attrs = [ 433 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 434 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 435 | ] 436 | black = [ 437 | {file = "black-20.8b1-py3-none-any.whl", hash = "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"}, 438 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, 439 | ] 440 | certifi = [ 441 | {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, 442 | {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, 443 | ] 444 | chardet = [ 445 | {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, 446 | {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, 447 | ] 448 | click = [ 449 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 450 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 451 | ] 452 | codecov = [ 453 | {file = "codecov-2.1.11-py2.py3-none-any.whl", hash = "sha256:ba8553a82942ce37d4da92b70ffd6d54cf635fc1793ab0a7dc3fecd6ebfb3df8"}, 454 | {file = "codecov-2.1.11-py3.8.egg", hash = "sha256:e95901d4350e99fc39c8353efa450050d2446c55bac91d90fcfd2354e19a6aef"}, 455 | {file = "codecov-2.1.11.tar.gz", hash = "sha256:6cde272454009d27355f9434f4e49f238c0273b216beda8472a65dc4957f473b"}, 456 | ] 457 | colorama = [ 458 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 459 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 460 | ] 461 | commonmark = [ 462 | {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, 463 | {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, 464 | ] 465 | coverage = [ 466 | {file = "coverage-5.3.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d"}, 467 | {file = "coverage-5.3.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7"}, 468 | {file = "coverage-5.3.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528"}, 469 | {file = "coverage-5.3.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044"}, 470 | {file = "coverage-5.3.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b"}, 471 | {file = "coverage-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297"}, 472 | {file = "coverage-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb"}, 473 | {file = "coverage-5.3.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899"}, 474 | {file = "coverage-5.3.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36"}, 475 | {file = "coverage-5.3.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500"}, 476 | {file = "coverage-5.3.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7"}, 477 | {file = "coverage-5.3.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f"}, 478 | {file = "coverage-5.3.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b"}, 479 | {file = "coverage-5.3.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec"}, 480 | {file = "coverage-5.3.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714"}, 481 | {file = "coverage-5.3.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b"}, 482 | {file = "coverage-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7"}, 483 | {file = "coverage-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72"}, 484 | {file = "coverage-5.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b"}, 485 | {file = "coverage-5.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4"}, 486 | {file = "coverage-5.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105"}, 487 | {file = "coverage-5.3.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448"}, 488 | {file = "coverage-5.3.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277"}, 489 | {file = "coverage-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f"}, 490 | {file = "coverage-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c"}, 491 | {file = "coverage-5.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd"}, 492 | {file = "coverage-5.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4"}, 493 | {file = "coverage-5.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff"}, 494 | {file = "coverage-5.3.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8"}, 495 | {file = "coverage-5.3.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e"}, 496 | {file = "coverage-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2"}, 497 | {file = "coverage-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879"}, 498 | {file = "coverage-5.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b"}, 499 | {file = "coverage-5.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497"}, 500 | {file = "coverage-5.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059"}, 501 | {file = "coverage-5.3.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631"}, 502 | {file = "coverage-5.3.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830"}, 503 | {file = "coverage-5.3.1-cp38-cp38-win32.whl", hash = "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae"}, 504 | {file = "coverage-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606"}, 505 | {file = "coverage-5.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f"}, 506 | {file = "coverage-5.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1"}, 507 | {file = "coverage-5.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8"}, 508 | {file = "coverage-5.3.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4"}, 509 | {file = "coverage-5.3.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d"}, 510 | {file = "coverage-5.3.1-cp39-cp39-win32.whl", hash = "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98"}, 511 | {file = "coverage-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1"}, 512 | {file = "coverage-5.3.1-pp36-none-any.whl", hash = "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3"}, 513 | {file = "coverage-5.3.1-pp37-none-any.whl", hash = "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c"}, 514 | {file = "coverage-5.3.1.tar.gz", hash = "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b"}, 515 | ] 516 | dataclasses = [ 517 | {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"}, 518 | {file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"}, 519 | ] 520 | flake8 = [ 521 | {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, 522 | {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, 523 | ] 524 | idna = [ 525 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 526 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 527 | ] 528 | importlib-metadata = [ 529 | {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"}, 530 | {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"}, 531 | ] 532 | iniconfig = [ 533 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 534 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 535 | ] 536 | isort = [ 537 | {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, 538 | {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, 539 | ] 540 | mccabe = [ 541 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 542 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 543 | ] 544 | mypy-extensions = [ 545 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 546 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 547 | ] 548 | packaging = [ 549 | {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, 550 | {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, 551 | ] 552 | pathspec = [ 553 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 554 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 555 | ] 556 | pluggy = [ 557 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 558 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 559 | ] 560 | py = [ 561 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 562 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 563 | ] 564 | pycodestyle = [ 565 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 566 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 567 | ] 568 | pyflakes = [ 569 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 570 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 571 | ] 572 | pyparsing = [ 573 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 574 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 575 | ] 576 | pytest = [ 577 | {file = "pytest-6.2.1-py3-none-any.whl", hash = "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8"}, 578 | {file = "pytest-6.2.1.tar.gz", hash = "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"}, 579 | ] 580 | pytest-cov = [ 581 | {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, 582 | {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, 583 | ] 584 | pyupgrade = [ 585 | {file = "pyupgrade-2.7.4-py2.py3-none-any.whl", hash = "sha256:ab2f47377e977bec8dd41db634fde35bce78fedd2be0e8b189fe687f23fb1d85"}, 586 | {file = "pyupgrade-2.7.4.tar.gz", hash = "sha256:e57057ccef3fd8e8fad5ba9f365c1288a076271a222ccb502d865c0d8fe16c3a"}, 587 | ] 588 | regex = [ 589 | {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, 590 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, 591 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, 592 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, 593 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, 594 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, 595 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, 596 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, 597 | {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, 598 | {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, 599 | {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, 600 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, 601 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, 602 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, 603 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, 604 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, 605 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, 606 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, 607 | {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, 608 | {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, 609 | {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, 610 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, 611 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, 612 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, 613 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, 614 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, 615 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, 616 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, 617 | {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, 618 | {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, 619 | {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, 620 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, 621 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, 622 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, 623 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, 624 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, 625 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, 626 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, 627 | {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, 628 | {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, 629 | {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, 630 | ] 631 | requests = [ 632 | {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, 633 | {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, 634 | ] 635 | tokenize-rt = [ 636 | {file = "tokenize_rt-4.0.0-py2.py3-none-any.whl", hash = "sha256:c47d3bd00857c24edefccdd6dc99c19d4ceed77c5971a3e2fac007fb0c02e39d"}, 637 | {file = "tokenize_rt-4.0.0.tar.gz", hash = "sha256:07d5f88b6a953612159b160129bcf9425677c8d062b0cb83250968ba803e1c64"}, 638 | ] 639 | toml = [ 640 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 641 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 642 | ] 643 | typed-ast = [ 644 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, 645 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, 646 | {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, 647 | {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, 648 | {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, 649 | {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, 650 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, 651 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, 652 | {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, 653 | {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, 654 | {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, 655 | {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, 656 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, 657 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, 658 | {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, 659 | {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, 660 | {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, 661 | {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, 662 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, 663 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, 664 | {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, 665 | {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, 666 | {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, 667 | {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, 668 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, 669 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, 670 | {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, 671 | {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, 672 | {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, 673 | {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, 674 | ] 675 | typing-extensions = [ 676 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 677 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 678 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 679 | ] 680 | urllib3 = [ 681 | {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, 682 | {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, 683 | ] 684 | zipp = [ 685 | {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, 686 | {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, 687 | ] 688 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pytest_markdown" 3 | version = "1.0.2" 4 | description = "Test your markdown docs with pytest" 5 | authors = ["John Carr "] 6 | license = "Apache-2.0" 7 | readme = "README.md" 8 | homepage = "https://github.com/Jc2k/pytest-markdown" 9 | keywords = ["pytest", "markdown"] 10 | classifiers=[ 11 | "License :: OSI Approved :: Apache Software License", 12 | "Topic :: Software Development :: Testing", 13 | "Intended Audience :: Developers", 14 | "Programming Language :: Python :: 3.7", 15 | "Programming Language :: Python :: 3.8", 16 | "Programming Language :: Python :: 3.9", 17 | "Development Status :: 4 - Beta" 18 | ] 19 | 20 | [tool.poetry.dependencies] 21 | python = "^3.6.1" 22 | pytest = "^6.0.1" 23 | CommonMark = "~0.9.1" 24 | 25 | [tool.poetry.plugins."pytest11"] 26 | markdown = "pytest_markdown.plugin" 27 | 28 | [tool.poetry.dev-dependencies] 29 | isort = "^5.5.1" 30 | black = "^20.8b1" 31 | flake8 = "^3.8.3" 32 | pytest-cov = "^2.10.1" 33 | pyupgrade = "^2.7.2" 34 | codecov = "^2.1.9" 35 | 36 | [tool.black] 37 | target-version = ["py36", "py37", "py38"] 38 | 39 | [tool.coverage.run] 40 | omit = ["tests/*"] 41 | 42 | [tool.isort] 43 | profile = "black" 44 | indent = " " 45 | force_sort_within_sections = "true" 46 | sections = "FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER" 47 | known_first_party = "pytest_docker_tools,tests" 48 | forced_separate = "tests" 49 | combine_as_imports = "true" 50 | extra_standard_library = "_socket" 51 | 52 | [build-system] 53 | requires = ["poetry>=0.12"] 54 | build-backend = "poetry.masonry.api" 55 | -------------------------------------------------------------------------------- /pytest_markdown/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | A py.test plugin that runs tests its found in your Markdown files. 3 | """ 4 | -------------------------------------------------------------------------------- /pytest_markdown/plugin.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from linecache import cache 3 | import types 4 | 5 | from _pytest.assertion.rewrite import rewrite_asserts 6 | from _pytest.python import Module 7 | import commonmark 8 | import py 9 | import pytest 10 | 11 | 12 | def create_pytest_module(name, path, source, config): 13 | """Return a Module from source string with its assertions rewritten""" 14 | try: 15 | tree = ast.parse(source) 16 | except SyntaxError: 17 | return None 18 | 19 | rewrite_asserts(tree, py.path.local(path), config) 20 | 21 | try: 22 | co = compile(tree, path, "exec", dont_inherit=True) 23 | except SyntaxError: 24 | return 25 | 26 | mod = types.ModuleType(name) 27 | exec(co, mod.__dict__) 28 | 29 | # Also, seed the line cache, so pytest can show source code 30 | def getsource(): 31 | return source 32 | 33 | cache[path] = (getsource,) 34 | 35 | return mod 36 | 37 | 38 | class MarkdownItem(Module): 39 | def __init__(self, fspath, parent, code, nodeid=None): 40 | self._code_obj = create_pytest_module( 41 | fspath.basename, fspath.basename, code, parent.config 42 | ) 43 | super().__init__(fspath, parent, nodeid=nodeid) 44 | 45 | def _getobj(self): 46 | return self._code_obj 47 | 48 | 49 | def collect_literals_from_children(token): 50 | literals = [] 51 | stack = [token.first_child] 52 | while stack: 53 | cur = stack.pop() 54 | if cur.literal: 55 | literals.append(cur.literal) 56 | if cur.nxt: 57 | stack.append(cur.nxt) 58 | if cur.first_child: 59 | stack.append(cur.first_child) 60 | return "".join(literals) 61 | 62 | 63 | class MarkdownCollector: 64 | def __init__(self, item): 65 | super().__init__() 66 | self.item = item 67 | self.ast = commonmark.Parser().parse(self.item.fspath.open().read()) 68 | self.stack = [ 69 | ( 70 | 0, 71 | self.item, 72 | self.item.name, 73 | ) 74 | ] 75 | self.collected = [] 76 | 77 | def collect(self): 78 | self.stack = [ 79 | ( 80 | 0, 81 | self.item, 82 | self.item.name, 83 | ) 84 | ] 85 | self.collected = [] 86 | 87 | walker = self.ast.walker() 88 | 89 | event = walker.nxt() 90 | while event is not None: 91 | if not event["entering"]: 92 | event = walker.nxt() 93 | continue 94 | 95 | func = "visit_" + event["node"].t 96 | if hasattr(self, func): 97 | getattr(self, func)(event["node"]) 98 | 99 | event = walker.nxt() 100 | 101 | return self.collected 102 | 103 | def visit_heading(self, token): 104 | while self.stack[-1][0] >= token.level: 105 | self.stack.pop() 106 | 107 | name = collect_literals_from_children(token).lower().replace(" ", "-") 108 | nodeid = "::".join(s[2] for s in self.stack) + "::" + name 109 | self.stack.append( 110 | ( 111 | token.level, 112 | pytest.Item.from_parent(self.stack[-1][1], name=name, nodeid=nodeid), 113 | name, 114 | ) 115 | ) 116 | 117 | def visit_code_block(self, token): 118 | if token.info != "python": 119 | return 120 | 121 | output = token.literal 122 | 123 | if output.startswith("\n"): 124 | return "" 125 | 126 | name = f"line_{token.sourcepos[0][0]}" 127 | 128 | if output.lower().strip().startswith("# conftest.py\n"): 129 | nodeid = self.stack[-1][1].nodeid 130 | else: 131 | nodeid = self.stack[-1][1].nodeid + "::" + name 132 | 133 | mi = MarkdownItem.from_parent( 134 | self.stack[-1][1], 135 | fspath=self.item.fspath, 136 | code=output, 137 | nodeid=nodeid, 138 | ) 139 | self.collected.append(mi) 140 | 141 | return "" 142 | 143 | 144 | class MarkdownFile(pytest.File): 145 | def collect(self): 146 | yield from MarkdownCollector(self).collect() 147 | 148 | 149 | def pytest_collect_file(parent, path): 150 | if path.ext == ".md": 151 | return MarkdownFile.from_parent(parent, fspath=path) 152 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | VERSION=${1:-patch} 4 | 5 | poetry version $VERSION 6 | git commit -a -m "Version bump" 7 | git tag -a -s `poetry version | awk '{ print $2; }'` 8 | git push --tags 9 | git push 10 | 11 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | set -e 3 | find . -name '*.py' -exec pyupgrade --py36-plus {} + 4 | python -m black tests pytest_markdown 5 | python -m isort tests pytest_markdown 6 | python -m black tests pytest_markdown --check --diff 7 | python -m flake8 tests pytest_markdown 8 | python -m pytest 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build 3 | doctests = True 4 | # To work with Black 5 | max-line-length = 88 6 | # E501: line too long 7 | # W503: Line break occurred before a binary operator 8 | # E203: Whitespace before ':' 9 | # D202 No blank lines allowed after function docstring 10 | # W504 line break after binary operator 11 | ignore = 12 | E501, 13 | W503, 14 | E203, 15 | D202, 16 | W504 17 | -------------------------------------------------------------------------------- /tests/integration/example.md: -------------------------------------------------------------------------------- 1 | # Header **1** 2 | 3 | You can define a conftest.py in the top of your markdown file: 4 | 5 | ```python 6 | # conftest.py 7 | 8 | import pytest 9 | 10 | @pytest.fixture 11 | def example(): 12 | return 1234 13 | ``` 14 | 15 | And you can have tests: 16 | 17 | ```python 18 | # test_1.py 19 | 20 | def test_1(example): 21 | assert example == 1234 22 | ``` 23 | 24 | There can be multiple code blocks in each section, and each code block can contain multiple tests: 25 | 26 | 27 | ```python 28 | # test_2.py 29 | 30 | def test_2(example): 31 | assert example == 1234 32 | 33 | def test_3(example): 34 | assert example + 1 == 1235 35 | ``` 36 | 37 | ## Header 2a 38 | 39 | Defining a new child - it can see the parent conftest, even if other fixtures are defined. 40 | 41 | ```python 42 | # conftest.py 43 | 44 | import pytest 45 | 46 | @pytest.fixture 47 | def example2(): 48 | return 4321 49 | ``` 50 | 51 | And then tests: 52 | 53 | ```python 54 | # test_1.py 55 | 56 | def test_1(example): 57 | assert example == 1234 58 | ``` 59 | 60 | ### Header **3a** 61 | 62 | Defining a new child - 3 levels deep - it can see the parent conftest. 63 | 64 | ```python 65 | # test_1.py 66 | 67 | def test_1(example, example2): 68 | assert example == 1234 69 | assert example2 == 4321 70 | ``` 71 | 72 | ## Header 2b 73 | 74 | You can define a conftest.py in a heading. It can override conftest.py stuff in the parent section: 75 | 76 | ```python 77 | # conftest.py 78 | 79 | import pytest 80 | 81 | @pytest.fixture 82 | def example(): 83 | return 1235 84 | ``` 85 | 86 | And you can have tests: 87 | 88 | ```python 89 | # test_1.py 90 | 91 | def test_1(example): 92 | assert example == 1235 93 | ``` 94 | 95 | ## Header 2c 96 | 97 | Defining a new sibling - it can see the root conftest but not its siblings. 98 | 99 | ```python 100 | # test_1.py 101 | 102 | def test_1(example): 103 | assert example == 1234 104 | ``` 105 | --------------------------------------------------------------------------------