Repository: twu/skjold (language: python)

Issue:
Support `pypa/advisory-db` source.
**Summary**

I will start playing around with the specification today and see if I get something to work. I haven't found information on how final the current API and specification is so I consider all of this _experimental_ and won't put it into _master_ just yet. Personally I prefer the _offline_ approach instead of asking an API for _every_ installed package.

**How will it work?**

`skjold` will either query the `osv.dev` API directly or will fetch the `master` tarball from the [`pypa/advisory-db`](https://github.com/pypa/advisory-db/) similarly to how the Gemansium source works (or both).

**Intended outcome**

Skjold supports fetching and checking data from the PyPA Advisory Database using `-s pypa` and/or `-s osv`.

## Links
- [OSV.dev API](https://osv.dev)
- [Github: `pypa/advisory-db`](https://github.com/pypa/advisory-db/)
- [Google Doc: A shared vulnerability format for open-source packages](https://docs.google.com/document/d/1sylBGNooKtf220RHQn1I8pZRmqXZQADDQ_TOABrKTpA)


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index e0e834b..c30c8f1 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,11 @@
 ## Introduction
 It currently supports fetching advisories from the following sources:
 
-- [GitHub Advisory Database](https://github.com/advisories)
-- [PyUP.io safety-db](https://github.com/pyupio/safety-db)
-- [GitLab gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db)
+- [GitHub Advisory Database](https://github.com/advisories) `github`
+- [PyUP.io safety-db](https://github.com/pyupio/safety-db) `pyup`
+- [GitLab gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) `gemnasium`
+- [PYPA Advisory Database](https://github.com/pypa/advisory-db) (EXPERIMENTAL) `pypa`
+- [OSV.dev Database](https://osv.dev) (EXPERIMENTAL) `osv`
 
 No source is enabled by default! Individual sources can be enabled by setting `sources` list (see [Configuration](#configuration)). There is (currently) no de-duplication meaning that using all of them could result in _a lot_ of duplicates.
 
diff --git a/mypy.ini b/mypy.ini
deleted file mode 100644
index 19bd09a..0000000
--- a/mypy.ini
+++ /dev/null
@@ -1,16 +0,0 @@
-[mypy]
-python_version = 3.7
-files=src,tests
-ignore_missing_imports = true
-warn_return_any = true
-warn_unused_configs = true
-warn_unused_ignores = true
-warn_redundant_casts = True
-disallow_untyped_defs = true
-disallow_untyped_calls = true
-follow_imports = skip
-no_implicit_optional = true
-warn_no_return = true
-strict_optional = True
-check_untyped_defs = True
-ignore_errors = False
diff --git a/poetry.lock b/poetry.lock
index 1ff5a30..2c1c686 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -16,17 +16,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "attrs"
-version = "20.3.0"
+version = "21.2.0"
 description = "Classes Without Boilerplate"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"]
-docs = ["furo", "sphinx", "zope.interface"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
 
 [[package]]
 name = "black"
@@ -103,7 +103,7 @@ python-versions = ">=3.6, <3.7"
 
 [[package]]
 name = "distlib"
-version = "0.3.1"
+version = "0.3.2"
 description = "Distribution utilities"
 category = "dev"
 optional = false
@@ -127,7 +127,7 @@ python-versions = "*"
 
 [[package]]
 name = "identify"
-version = "1.5.11"
+version = "1.6.2"
 description = "File identification library for Python"
 category = "dev"
 optional = false
@@ -138,7 +138,7 @@ license = ["editdistance"]
 
 [[package]]
 name = "importlib-metadata"
-version = "3.3.0"
+version = "4.5.0"
 description = "Read metadata from Python packages"
 category = "main"
 optional = false
@@ -149,23 +149,23 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
 
 [[package]]
 name = "importlib-resources"
-version = "4.1.1"
+version = "5.1.4"
 description = "Read resources from Python packages"
 category = "dev"
 optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
-zipp = {version = ">=0.4", markers = "python_version < \"3.8\""}
+zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
 
 [package.extras]
-docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "pytest-black (>=0.3.7)", "pytest-mypy"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [[package]]
 name = "iniconfig"
@@ -203,7 +203,7 @@ python-versions = "*"
 
 [[package]]
 name = "nodeenv"
-version = "1.5.0"
+version = "1.6.0"
 description = "Node.js virtual environment builder"
 category = "dev"
 optional = false
@@ -211,7 +211,7 @@ python-versions = "*"
 
 [[package]]
 name = "packaging"
-version = "20.8"
+version = "20.9"
 description = "Core utilities for Python packages"
 category = "dev"
 optional = false
@@ -373,7 +373,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 
 [[package]]
 name = "regex"
-version = "2020.11.13"
+version = "2021.4.4"
 description = "Alternative regular expression module, to replace re."
 category = "dev"
 optional = false
@@ -381,7 +381,7 @@ python-versions = "*"
 
 [[package]]
 name = "six"
-version = "1.15.0"
+version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
 category = "dev"
 optional = false
@@ -405,15 +405,31 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "typed-ast"
-version = "1.4.2"
+version = "1.4.3"
 description = "a fork of Python 2 and 3 ast modules with type comment support"
 category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "types-pyyaml"
+version = "0.1.8"
+description = "Typing stubs for PyYAML"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "types-toml"
+version = "0.1.2"
+description = "Typing stubs for toml"
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "typing-extensions"
-version = "3.7.4.3"
+version = "3.10.0.0"
 description = "Backported and Experimental Type Hints for Python 3.5+"
 category = "main"
 optional = false
@@ -421,7 +437,7 @@ python-versions = "*"
 
 [[package]]
 name = "virtualenv"
-version = "20.2.2"
+version = "20.4.7"
 description = "Virtual Python Environment builder"
 category = "dev"
 optional = false
@@ -437,11 +453,11 @@ six = ">=1.9.0,<2"
 
 [package.extras]
 docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
-testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]
+testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]
 
 [[package]]
 name = "watchdog"
-version = "1.0.2"
+version = "2.1.2"
 description = "Filesystem events monitoring"
 category = "dev"
 optional = false
@@ -452,20 +468,20 @@ watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"]
 
 [[package]]
 name = "zipp"
-version = "3.4.0"
+version = "3.4.1"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 category = "main"
 optional = false
 python-versions = ">=3.6"
 
 [package.extras]
-docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=3.5,!=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"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.6"
-content-hash = "6d73926cf45a0e40ae42342cbb21fd520d0b14ac48a622d0e9d634d6ca5df55a"
+content-hash = "8c04387bd67d4b36adb3cae881109c2ee8b3915b45cc799d088756c1c3cc606e"
 
 [metadata.files]
 appdirs = [
@@ -477,8 +493,8 @@ atomicwrites = [
     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
 ]
 attrs = [
-    {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
-    {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
+    {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
+    {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
 ]
 black = [
     {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
@@ -554,8 +570,8 @@ dataclasses = [
     {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
 ]
 distlib = [
-    {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
-    {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
+    {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"},
+    {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"},
 ]
 docopt = [
     {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
@@ -565,16 +581,16 @@ filelock = [
     {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
 ]
 identify = [
-    {file = "identify-1.5.11-py2.py3-none-any.whl", hash = "sha256:7aef7a5104d6254c162990e54a203cdc0fd202046b6c415bd5d636472f6565c4"},
-    {file = "identify-1.5.11.tar.gz", hash = "sha256:b2c71bf9f5c482c389cef816f3a15f1c9d7429ad70f497d4a2e522442d80c6de"},
+    {file = "identify-1.6.2-py2.py3-none-any.whl", hash = "sha256:8f9879b5b7cca553878d31548a419ec2f227d3328da92fe8202bc5e546d5cbc3"},
+    {file = "identify-1.6.2.tar.gz", hash = "sha256:1c2014f6985ed02e62b2e6955578acf069cb2c54859e17853be474bfe7e13bed"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"},
-    {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"},
+    {file = "importlib_metadata-4.5.0-py3-none-any.whl", hash = "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00"},
+    {file = "importlib_metadata-4.5.0.tar.gz", hash = "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139"},
 ]
 importlib-resources = [
-    {file = "importlib_resources-4.1.1-py3-none-any.whl", hash = "sha256:0a948d0c8c3f9344de62997e3f73444dbba233b1eaf24352933c2d264b9e4182"},
-    {file = "importlib_resources-4.1.1.tar.gz", hash = "sha256:6b45007a479c4ec21165ae3ffbe37faf35404e2041fac6ae1da684f38530ca73"},
+    {file = "importlib_resources-5.1.4-py3-none-any.whl", hash = "sha256:e962bff7440364183203d179d7ae9ad90cb1f2b74dcb84300e88ecc42dca3351"},
+    {file = "importlib_resources-5.1.4.tar.gz", hash = "sha256:54161657e8ffc76596c4ede7080ca68cb02962a2e074a2586b695a93a925d36e"},
 ]
 iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@@ -610,12 +626,12 @@ mypy-extensions = [
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
 nodeenv = [
-    {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
-    {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"},
+    {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
+    {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
 ]
 packaging = [
-    {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
-    {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
+    {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
+    {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
 ]
 pathspec = [
     {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
@@ -666,76 +682,68 @@ pyyaml = [
     {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
     {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
     {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
     {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
     {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
     {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
     {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
     {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
     {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
     {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
     {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
-    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
-    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
     {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
     {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
     {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
     {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
-    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
-    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
     {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
     {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
     {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
 ]
 regex = [
-    {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"},
-    {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"},
-    {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"},
-    {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"},
-    {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"},
-    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"},
-    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"},
-    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"},
-    {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"},
-    {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"},
-    {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"},
-    {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"},
-    {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"},
-    {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"},
-    {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"},
-    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"},
-    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"},
-    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"},
-    {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"},
-    {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"},
-    {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"},
-    {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"},
-    {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"},
-    {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"},
-    {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"},
-    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"},
-    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"},
-    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"},
-    {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"},
-    {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"},
-    {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"},
-    {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"},
-    {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"},
-    {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"},
-    {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"},
-    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"},
-    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"},
-    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"},
-    {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"},
-    {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"},
-    {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"},
+    {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"},
+    {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"},
+    {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"},
+    {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"},
+    {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"},
+    {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"},
+    {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"},
+    {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"},
+    {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"},
+    {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"},
+    {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"},
+    {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"},
+    {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"},
 ]
 six = [
-    {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
-    {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
 ]
 termcolor = [
     {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
@@ -745,66 +753,72 @@ toml = [
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
 typed-ast = [
-    {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"},
-    {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"},
-    {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"},
-    {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"},
-    {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"},
-    {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"},
-    {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"},
-    {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"},
-    {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"},
-    {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"},
-    {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"},
-    {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"},
-    {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"},
-    {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"},
-    {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"},
-    {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"},
-    {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"},
-    {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"},
-    {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"},
-    {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"},
-    {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"},
-    {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"},
-    {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"},
-    {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"},
-    {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"},
-    {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"},
-    {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"},
-    {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"},
-    {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"},
-    {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"},
+    {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"},
+    {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"},
+    {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"},
+    {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"},
+    {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"},
+    {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"},
+    {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"},
+    {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"},
+    {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"},
+    {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"},
+    {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"},
+    {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
+    {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
+]
+types-pyyaml = [
+    {file = "types_PyYAML-0.1.8-py2.py3-none-any.whl", hash = "sha256:8db1c8ceed4f6e86d54484e40709ad5f684a2f780308ba7a5e3850be9fb5961a"},
+]
+types-toml = [
+    {file = "types_toml-0.1.2-py2.py3-none-any.whl", hash = "sha256:92fe51b57534bc5c16da2d5e2a712aa229c86f8872df95d5f58dcb6977a376ce"},
 ]
 typing-extensions = [
-    {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
-    {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
-    {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
+    {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
+    {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
+    {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
 ]
 virtualenv = [
-    {file = "virtualenv-20.2.2-py2.py3-none-any.whl", hash = "sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c"},
-    {file = "virtualenv-20.2.2.tar.gz", hash = "sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b"},
+    {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"},
+    {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"},
 ]
 watchdog = [
-    {file = "watchdog-1.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e2a531e71be7b5cc3499ae2d1494d51b6a26684bcc7c3146f63c810c00e8a3cc"},
-    {file = "watchdog-1.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e7c73edef48f4ceeebb987317a67e0080e5c9228601ff67b3c4062fa020403c7"},
-    {file = "watchdog-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85e6574395aa6c1e14e0f030d9d7f35c2340a6cf95d5671354ce876ac3ffdd4d"},
-    {file = "watchdog-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:27d9b4666938d5d40afdcdf2c751781e9ce36320788b70208d0f87f7401caf93"},
-    {file = "watchdog-1.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2f1ade0d0802503fda4340374d333408831cff23da66d7e711e279ba50fe6c4a"},
-    {file = "watchdog-1.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f1d0e878fd69129d0d68b87cee5d9543f20d8018e82998efb79f7e412d42154a"},
-    {file = "watchdog-1.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d948ad9ab9aba705f9836625b32e965b9ae607284811cd98334423f659ea537a"},
-    {file = "watchdog-1.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:101532b8db506559e52a9b5d75a308729b3f68264d930670e6155c976d0e52a0"},
-    {file = "watchdog-1.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:b1d723852ce90a14abf0ec0ca9e80689d9509ee4c9ee27163118d87b564a12ac"},
-    {file = "watchdog-1.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:68744de2003a5ea2dfbb104f9a74192cf381334a9e2c0ed2bbe1581828d50b61"},
-    {file = "watchdog-1.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:602dbd9498592eacc42e0632c19781c3df1728ef9cbab555fab6778effc29eeb"},
-    {file = "watchdog-1.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:016b01495b9c55b5d4126ed8ae75d93ea0d99377084107c33162df52887cee18"},
-    {file = "watchdog-1.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:5f1f3b65142175366ba94c64d8d4c8f4015825e0beaacee1c301823266b47b9b"},
-    {file = "watchdog-1.0.2-py3-none-win32.whl", hash = "sha256:57f05e55aa603c3b053eed7e679f0a83873c540255b88d58c6223c7493833bac"},
-    {file = "watchdog-1.0.2-py3-none-win_amd64.whl", hash = "sha256:f84146f7864339c8addf2c2b9903271df21d18d2c721e9a77f779493234a82b5"},
-    {file = "watchdog-1.0.2-py3-none-win_ia64.whl", hash = "sha256:ee21aeebe6b3e51e4ba64564c94cee8dbe7438b9cb60f0bb350c4fa70d1b52c2"},
-    {file = "watchdog-1.0.2.tar.gz", hash = "sha256:376cbc2a35c0392b0fe7ff16fbc1b303fd99d4dd9911ab5581ee9d69adc88982"},
+    {file = "watchdog-2.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:581e3548159fe7d2a9f377a1fbcb41bdcee46849cca8ab803c7ac2e5e04ec77c"},
+    {file = "watchdog-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:edcd9ef3fd460bb8a98eb1fcf99941e9fd9f275f45f1a82cb1359ec92975d647"},
+    {file = "watchdog-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d34ce2261f118ecd57eedeef95fc2a495fc4a40b3ed7b3bf0bd7a8ccc1ab4f8f"},
+    {file = "watchdog-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:668391e6c32742d76e5be5db6bf95c455fa4b3d11e76a77c13b39bccb3a47a72"},
+    {file = "watchdog-2.1.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6ef9fe57162c4c361692620e1d9167574ba1975ee468b24051ca11c9bba6438e"},
+    {file = "watchdog-2.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:58ebb1095ee493008a7789d47dd62e4999505d82be89fc884d473086fccc6ebd"},
+    {file = "watchdog-2.1.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:91387ee2421f30b75f7ff632c9d48f76648e56bf346a7c805c0a34187a93aab4"},
+    {file = "watchdog-2.1.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:a6471517315a8541a943c00b45f1d252e36898a3ae963d2d52509b89a50cb2b9"},
+    {file = "watchdog-2.1.2-py3-none-manylinux2014_i686.whl", hash = "sha256:a42e6d652f820b2b94cd03156c62559a2ea68d476476dfcd77d931e7f1012d4a"},
+    {file = "watchdog-2.1.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:3d6405681471ebe0beb3aa083998c4870e48b57f8afdb45ea1b5957cc5cf1014"},
+    {file = "watchdog-2.1.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:598d772beeaf9c98d0df946fbabf0c8365dd95ea46a250c224c725fe0c4730bc"},
+    {file = "watchdog-2.1.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:4b219d46d89cfa49af1d73175487c14a318a74cb8c5442603fd13c6a5b418c86"},
+    {file = "watchdog-2.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:188145185c08c73c56f1478ccf1f0f0f85101191439679b35b6b100886ce0b39"},
+    {file = "watchdog-2.1.2-py3-none-win32.whl", hash = "sha256:255a32d44bbbe62e52874ff755e2eefe271b150e0ec240ad7718a62a7a7a73c4"},
+    {file = "watchdog-2.1.2-py3-none-win_amd64.whl", hash = "sha256:1a62a4671796dc93d1a7262286217d9e75823c63d4c42782912d39a506d30046"},
+    {file = "watchdog-2.1.2-py3-none-win_ia64.whl", hash = "sha256:104266a778906ae0e971368d368a65c4cd032a490a9fca5ba0b78c6c7ae11720"},
+    {file = "watchdog-2.1.2.tar.gz", hash = "sha256:0237db4d9024859bea27d0efb59fe75eef290833fd988b8ead7a879b0308c2db"},
 ]
 zipp = [
-    {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
-    {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
+    {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"},
+    {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"},
 ]
diff --git a/pyproject.toml b/pyproject.toml
index 09fe6e7..7af862a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,7 +17,7 @@ keywords = [
 ]
 # https://pypi.org/classifiers/
 classifiers = [
-    "Development Status :: 3 - Alpha",
+    "Development Status :: 4 - Beta",
     "Environment :: Console",
     "Intended Audience :: Developers",
     "License :: OSI Approved :: MIT License",
@@ -31,6 +31,7 @@ classifiers = [
     "Topic :: Software Development :: Quality Assurance",
     "Topic :: Software Development :: Build Tools",
     "Topic :: Utilities",
+    "Topic :: Security",
     "Typing :: Typed",
 ]
 
@@ -52,17 +53,19 @@ toml = ">=0.10.0"
 pytest-watch = "^4.2.0"
 pytest-sugar = "^0.9.4"
 pytest-mock = "^3.6.1"
-pre-commit = '>=1.20.0'
-black = '>=19.10b0'
+pre-commit = ">=1.20.0"
+black = ">=19.10b0"
 mypy = "^0.902"
 coverage = {extras = ["toml"], version = "^5.5"}
 pytest-cov = "^2.12.1"
 pytest = "^6.2.4"
+types-toml = "^0.1.2"
+types-PyYAML = "^0.1.8"
 
 [tool.skjold]
 report_only = true
-report_format = 'json'
-sources = ["pyup", "github", "gemnasium"]
+report_format = 'cli'
+sources = ["pyup", "github", "gemnasium", "osv", "pypa"]
 cache_dir = ".skjold_cache"
 cache_expires = 43200
 
@@ -92,6 +95,23 @@ exclude = '''
 )/
 '''
 
+[tool.mypy]
+python_version = "3.7"
+files = ["src", "tests"]
+ignore_missing_imports = true
+warn_return_any = true
+warn_unused_configs = true
+warn_unused_ignores = true
+warn_redundant_casts = true
+disallow_untyped_defs = true
+disallow_untyped_calls = true
+follow_imports = "skip"
+no_implicit_optional = true
+warn_no_return = true
+strict_optional = true
+check_untyped_defs = true
+ignore_errors = false
+
 [tool.coverage.run]
 branch = true
 source = ["src"]
diff --git a/src/skjold/models.py b/src/skjold/models.py
index 66fbcac..c142bc1 100644
--- a/src/skjold/models.py
+++ b/src/skjold/models.py
@@ -139,7 +139,7 @@ def populate_from_cache(self) -> None:
     @abstractmethod
     def is_vulnerable_package(
         self, package_name: str, package_version: str
-    ) -> Tuple[bool, List[SecurityAdvisory]]:
+    ) -> Tuple[bool, Sequence[SecurityAdvisory]]:
         raise NotImplementedError
 
     @abstractmethod
diff --git a/src/skjold/sources/__init__.py b/src/skjold/sources/__init__.py
index 72d34cf..d928612 100644
--- a/src/skjold/sources/__init__.py
+++ b/src/skjold/sources/__init__.py
@@ -1,3 +1,7 @@
 from .github import Github
 from .pyup import PyUp
 from .gemnasium import Gemnasium
+from .pypa import PyPAAdvisoryDB
+from .osv import OSV
+
+__all__ = ("Github", "PyUp", "Gemnasium", "OSV", "PyPAAdvisoryDB")
diff --git a/src/skjold/sources/gemnasium.py b/src/skjold/sources/gemnasium.py
index cd3cc19..8aa7f89 100644
--- a/src/skjold/sources/gemnasium.py
+++ b/src/skjold/sources/gemnasium.py
@@ -106,7 +106,7 @@ def populate_from_cache(self) -> None:
                     doc = yaml.load(obj_fh, Loader=yaml.SafeLoader)
                     advisory = GemnasiumSecurityAdvisory.using(doc)
                     self._advisories[advisory.package_name.lower()].append(advisory)
-                else:
+                else:  # pragma: no cover
                     raise SkjoldException(
                         f"Unable to extract '{obj.name}' from source archive."
                     )
diff --git a/src/skjold/sources/osv.py b/src/skjold/sources/osv.py
new file mode 100644
index 0000000..4ebd90f
--- /dev/null
+++ b/src/skjold/sources/osv.py
@@ -0,0 +1,176 @@
+import json
+import os
+import urllib.request
+from typing import List, Tuple, Callable, Dict, Any, Sequence
+from typing import Optional, MutableMapping
+
+import semver
+
+from skjold.models import SecurityAdvisory, SecurityAdvisorySource, SecurityAdvisoryList
+from skjold.tasks import register_source
+
+
+def _is_supported_range_type(affected_range: Dict) -> bool:
+    """Return True if the given `affected.ranges` item is supported."""
+    return "type" in affected_range and affected_range["type"] in {
+        "ECOSYSTEM",
+        "SEMVER",
+    }
+
+
+def _osv_dev_api_request(
+    package_name: str, package_version: str, ecosystem: str = "PyPI"
+) -> Any:
+    """Return list of vulnerabilities for a given `package_name` and `package_version` via OSV.dev API."""
+
+    _api_token = os.environ.get("SKJOLD_OSV_API_TOKEN", None)
+
+    payload = json.dumps(
+        {
+            "version": package_version,
+            "package": {"name": package_name, "ecosystem": ecosystem},
+        }
+    ).encode("utf-8")
+    request_ = urllib.request.Request(
+        url="https://api.osv.dev/v1/query",
+        data=payload,
+        headers={
+            "Accept": "application/json",
+            # "Authorization": f"Bearer {_api_token}",
+            "Content-Type": "application/json; charset=utf-8",
+        },
+    )
+    with urllib.request.urlopen(request_) as response:
+        _data = json.loads(response.read())
+
+    return _data.get("vulns", [])
+
+
+class OSVSecurityAdvisory(SecurityAdvisory):
+    _json: dict
+
+    @classmethod
+    def using(cls, json_: dict) -> "OSVSecurityAdvisory":
+        obj = cls()
+        obj._json = json_
+        return obj
+
+    @property
+    def identifier(self) -> str:
+        return str(self._json["id"])
+
+    @property
+    def source(self) -> str:
+        return "osv"
+
+    @property
+    def severity(self) -> str:
+        return "UNKNOWN"
+
+    @property
+    def url(self) -> str:
+        return str(self.references[0])
+
+    @property
+    def references(self) -> List[str]:
+        return [str(reference["url"]) for reference in self._json["references"]]
+
+    @property
+    def package_name(self) -> str:
+        return str(self._json["package"]["name"]).strip()
+
+    @property
+    def summary(self) -> str:
+        return f"{self._json['details']}"
+
+    @property
+    def vulnerable_version_range(self) -> List[semver.VersionConstraint]:
+        # Try using ranges first to avoid clutter.
+        affected_ecosystem_ranges = list(
+            filter(
+                _is_supported_range_type,
+                self._json.get("affects", {}).get("ranges", []),
+            )
+        )
+        if len(affected_ecosystem_ranges):
+            ranges = []
+            for affected_range in affected_ecosystem_ranges:
+                _constraints = []
+                if "introduced" in affected_range:
+                    v = affected_range.get("introduced")
+                    _constraints.append(f">={v}")
+
+                if "fixed" in affected_range:
+                    v = affected_range.get("fixed")
+                    _constraints.append(f"<{v}")
+
+                ranges.append(semver.parse_constraint(",".join(_constraints)))
+            return ranges
+
+        # Try using versions (default).
+        affected_versions = self._json.get("affects", {}).get("versions", [])
+        if len(affected_versions):
+            return [semver.parse_constraint(x) for x in affected_versions]
+
+        return [semver.parse_constraint(">=0.0.0")]
+
+    @property
+    def vulnerable_versions(self) -> str:
+        return "||".join([str(x) for x in self.vulnerable_version_range])
+
+    def is_affected(self, version: str) -> bool:
+        version_ = semver.Version.parse(version)
+        allows_: Callable[[semver.VersionConstraint], bool] = (
+            lambda x: True if x.allows(version_) else False
+        )
+        # affected_versions = map(lambda x: x.allows(version), self.vulnerable_version_range)
+        affected_versions = map(allows_, self.vulnerable_version_range)
+        return any(affected_versions)
+
+
+class OSV(SecurityAdvisorySource):
+
+    _name = "osv"
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @property
+    def path(self) -> Optional[str]:
+        return None
+
+    def populate_from_cache(self) -> None:
+        pass
+
+    @property
+    def total_count(self) -> int:
+        return 0
+
+    def update(self) -> None:
+        pass
+
+    def has_security_advisory_for(self, package_name: str) -> bool:
+        """Always return `True` since to ensure we always call the OSV API for every package."""
+        return True
+
+    def is_vulnerable_package(
+        self, package_name: str, package_version: str
+    ) -> Tuple[bool, Sequence[SecurityAdvisory]]:
+
+        findings = _osv_dev_api_request(package_name.strip().lower(), package_version)
+        if not len(findings):
+            return False, []
+
+        advisories = []
+        for finding in findings:
+            advisory = OSVSecurityAdvisory.using(finding)
+            advisories.append(advisory)
+
+        return True, advisories
+
+    def get_security_advisories(self) -> MutableMapping[str, SecurityAdvisoryList]:
+        raise NotImplementedError
+
+
+register_source("osv", OSV)
diff --git a/src/skjold/sources/pypa.py b/src/skjold/sources/pypa.py
new file mode 100644
index 0000000..8265c77
--- /dev/null
+++ b/src/skjold/sources/pypa.py
@@ -0,0 +1,75 @@
+import os
+import tarfile
+import urllib.request
+from collections import defaultdict
+from typing import List, Tuple
+
+import yaml
+
+from skjold.models import SecurityAdvisory, SecurityAdvisorySource, SkjoldException
+from skjold.sources.osv import OSVSecurityAdvisory
+from skjold.tasks import register_source
+
+
+class PyPAAdvisoryDB(SecurityAdvisorySource):
+
+    _url = "https://api.github.com/repos/pypa/advisory-db/tarball"
+    _name = "pypa"
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @property
+    def path(self) -> str:
+        return os.path.join(self._cache_dir, "pypa.cache")
+
+    def populate_from_cache(self) -> None:
+        self._advisories = defaultdict(list)
+        with tarfile.TarFile.open(self.path, mode="r:gz") as archive:
+            pypi_advisories = filter(
+                lambda obj: "/vulns/" in obj.name and obj.name.endswith(".yaml"),
+                archive.getmembers(),
+            )
+
+            for obj in list(pypi_advisories):
+                obj_fh = archive.extractfile(obj.name)
+                if obj_fh:
+                    doc = yaml.load(obj_fh, Loader=yaml.SafeLoader)
+                    advisory = OSVSecurityAdvisory.using(doc)
+                    self._advisories[advisory.package_name.lower()].append(advisory)
+                else:  # pragma: no cover
+                    raise SkjoldException(
+                        f"Unable to extract '{obj.name}' from source archive."
+                    )
+
+    @property
+    def total_count(self) -> int:
+        return len(self._advisories.keys())
+
+    def update(self) -> None:
+        request = urllib.request.Request(
+            url=self._url, headers={"User-Agent": "Mozilla/5.0"}
+        )
+        with urllib.request.urlopen(request) as response:
+            with open(self.path, "wb") as fh:
+                fh.write(response.read())
+
+    def has_security_advisory_for(self, package_name: str) -> bool:
+        return package_name.strip().lower() in self.advisories.keys()
+
+    def is_vulnerable_package(
+        self, package_name: str, package_version: str
+    ) -> Tuple[bool, List[SecurityAdvisory]]:
+        if not self.has_security_advisory_for(package_name):
+            return False, []
+
+        advisories = []
+        for candidate in self.advisories[package_name.strip().lower()]:
+            if candidate.is_affected(package_version):
+                advisories.append(candidate)
+
+        return len(advisories) > 0, advisories
+
+
+register_source("pypa", PyPAAdvisoryDB)
diff --git a/src/skjold/tasks.py b/src/skjold/tasks.py
index 6e22f10..9498c70 100644
--- a/src/skjold/tasks.py
+++ b/src/skjold/tasks.py
@@ -62,7 +62,7 @@ def use(self, config: Dict) -> None:
 
     @property
     def app_home(self) -> str:
-        return click.get_app_dir("skjold", roaming=False, force_posix=True)
+        return str(click.get_app_dir("skjold", roaming=False, force_posix=True))
 
     @property
     def default_cache_dir(self) -> str:
@@ -145,6 +145,9 @@ def report(configuration: Configuration, results: List[Dict[str, Any]]) -> None:
         click.secho("")
         click.secho(textwrap.fill(result["summary"], 79), fg="white")
         click.secho(result["url"], fg="green")
+        click.secho("")
+        for reference in result["references"]:
+            click.secho(reference, fg="white")
         click.secho("-- ")
 
 


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 18e88f1..326d2a9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -64,3 +64,13 @@ jobs:
         PYTHONPATH: src
       run: |
         echo "urllib3==1.22" | poetry run skjold -v audit -o json -r -s pyup -
+    - name: Run pypa example
+      env:
+        PYTHONPATH: src
+      run: |
+        echo "urllib3==1.22" | poetry run skjold -v audit -o json -r -s pypa -
+    - name: Run osv example
+      env:
+        PYTHONPATH: src
+      run: |
+        echo "urllib3==1.22" | poetry run skjold -v audit -o json -r -s osv -
diff --git a/tests/conftest.py b/tests/conftest.py
index b7cf28c..a36635a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import os
 import tempfile
 from typing import Generator
diff --git a/tests/fixtures/osv/PYSEC-2021-54.yaml b/tests/fixtures/osv/PYSEC-2021-54.yaml
new file mode 100644
index 0000000..b581831
--- /dev/null
+++ b/tests/fixtures/osv/PYSEC-2021-54.yaml
@@ -0,0 +1,229 @@
+id: PYSEC-2021-54
+package:
+  name: salt
+  ecosystem: PyPI
+details: In SaltStack Salt before 3002.5, eauth tokens can be used once after expiration.
+  (They might be used to run command against the salt master or minions.)
+affects:
+  ranges:
+  - type: ECOSYSTEM
+    fixed: 2015.8.10
+  - type: ECOSYSTEM
+    introduced: 2015.8.11
+    fixed: 2015.8.13
+  - type: ECOSYSTEM
+    introduced: 2016.3.0
+    fixed: 2016.3.4
+  - type: ECOSYSTEM
+    introduced: 2016.3.5
+    fixed: 2016.3.6
+  - type: ECOSYSTEM
+    introduced: 2016.3.7
+    fixed: 2016.3.8
+  - type: ECOSYSTEM
+    introduced: 2016.11.0
+    fixed: 2016.11.3
+  - type: ECOSYSTEM
+    introduced: 2016.11.4
+    fixed: 2016.11.5
+  - type: ECOSYSTEM
+    introduced: 2016.11.7
+    fixed: 2016.11.10
+  - type: ECOSYSTEM
+    introduced: 2017.7.0
+    fixed: 2017.7.8
+  - type: ECOSYSTEM
+    introduced: 2018.3.0rc1
+    fixed: 2019.2.0rc1
+  - type: ECOSYSTEM
+    introduced: 2019.2.0
+    fixed: 2019.2.5
+  - type: ECOSYSTEM
+    introduced: 2019.2.6
+    fixed: 2019.2.8
+  - type: ECOSYSTEM
+    introduced: '3000'
+    fixed: '3000.6'
+  - type: ECOSYSTEM
+    introduced: '3001'
+    fixed: '3001.4'
+  - type: ECOSYSTEM
+    introduced: '3002'
+    fixed: '3002.5'
+  versions:
+  - 0.8.7
+  - 0.8.9
+  - 0.9.0
+  - 0.9.1
+  - 0.9.2
+  - 0.9.3
+  - 0.9.4
+  - 0.9.5
+  - 0.9.6
+  - 0.9.7
+  - 0.9.8
+  - 0.9.9
+  - 0.9.9.1
+  - 0.10.0
+  - 0.10.1
+  - 0.10.2
+  - 0.10.3
+  - 0.10.4
+  - 0.10.5
+  - 0.11.0
+  - 0.11.1
+  - 0.12.0
+  - 0.12.1
+  - 0.13.0
+  - 0.13.1
+  - 0.13.2
+  - 0.13.3
+  - 0.14.0
+  - 0.14.1
+  - 0.15.0
+  - 0.15.1
+  - 0.15.2
+  - 0.15.3
+  - 0.15.90
+  - 0.16.0
+  - 0.16.1
+  - 0.16.2
+  - 0.16.3
+  - 0.16.4
+  - 0.17.0rc1
+  - 0.17.0
+  - 0.17.1
+  - 0.17.2
+  - 0.17.3
+  - 0.17.4
+  - 0.17.5
+  - 2014.1.0rc1
+  - 2014.1.0rc2
+  - 2014.1.0rc3
+  - 2014.1.0
+  - 2014.1.1
+  - 2014.1.2
+  - 2014.1.3
+  - 2014.1.4
+  - 2014.1.5
+  - 2014.1.6
+  - 2014.1.7
+  - 2014.1.8
+  - 2014.1.9
+  - 2014.1.10
+  - 2014.1.11
+  - 2014.1.12
+  - 2014.1.13
+  - 2014.7.0rc1
+  - 2014.7.0rc2
+  - 2014.7.0rc3
+  - 2014.7.0rc4
+  - 2014.7.0rc5
+  - 2014.7.0rc6
+  - 2014.7.0rc7
+  - 2014.7.0
+  - 2014.7.1
+  - 2014.7.2
+  - 2014.7.3
+  - 2014.7.4
+  - 2014.7.5
+  - 2014.7.6
+  - 2014.7.7
+  - 2015.2.0rc1
+  - 2015.2.0rc2
+  - 2015.5.0
+  - 2015.5.1
+  - 2015.5.2
+  - 2015.5.3
+  - 2015.5.4
+  - 2015.5.5
+  - 2015.5.6
+  - 2015.5.7
+  - 2015.5.8
+  - 2015.5.9
+  - 2015.5.10
+  - 2015.5.11
+  - 2015.8.0rc1
+  - 2015.8.0rc2
+  - 2015.8.0rc3
+  - 2015.8.0rc4
+  - 2015.8.0rc5
+  - 2015.8.0
+  - 2015.8.1
+  - 2015.8.2
+  - 2015.8.3
+  - 2015.8.4
+  - 2015.8.5
+  - 2015.8.7
+  - 2015.8.8
+  - 2015.8.8.2
+  - 2015.8.9
+  - 2015.8.11
+  - 2015.8.12
+  - 2016.3.0
+  - 2016.3.1
+  - 2016.3.2
+  - 2016.3.3
+  - 2016.3.5
+  - 2016.3.7
+  - 2016.11.0
+  - 2016.11.1
+  - 2016.11.2
+  - 2016.11.4
+  - 2016.11.7
+  - 2016.11.8
+  - 2016.11.9
+  - 2017.7.0
+  - 2017.7.1
+  - 2017.7.2
+  - 2017.7.3
+  - 2017.7.4
+  - 2017.7.5
+  - 2017.7.6
+  - 2017.7.7
+  - 2018.3.0rc1
+  - 2018.3.0
+  - 2018.3.1
+  - 2018.3.2
+  - 2018.3.3
+  - 2018.3.4
+  - 2018.3.5
+  - 2019.2.0
+  - 2019.2.1
+  - 2019.2.2
+  - 2019.2.3
+  - 2019.2.4
+  - 2019.2.6
+  - 2019.2.7
+  - '3000'
+  - '3000.1'
+  - '3000.2'
+  - '3000.3'
+  - '3000.4'
+  - '3000.5'
+  - '3001'
+  - '3001.1'
+  - '3001.2'
+  - '3001.3'
+  - '3002'
+  - '3002.1'
+  - '3002.2'
+  - '3002.3'
+  - '3002.4'
+aliases:
+- CVE-2021-3144
+modified: '2021-03-31T14:15:00Z'
+published: '2021-02-27T05:15:00Z'
+references:
+- type: WEB
+  url: https://github.com/saltstack/salt/releases
+- type: WEB
+  url: https://saltproject.io/security_announcements/active-saltstack-cve-release-2021-feb-25/
+- type: WEB
+  url: https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YOGNT2XWPOYV7YT75DN7PS4GIYWFKOK5/
+- type: WEB
+  url: https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/7GRVZ5WAEI3XFN2BDTL6DDXFS5HYSDVB/
+- type: WEB
+  url: https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/FUGLOJ6NXLCIFRD2JTXBYQEMAEF2B6XH/
+- type: WEB
+  url: https://security.gentoo.org/glsa/202103-01
diff --git a/tests/fixtures/osv/introduced-and-fixed.yaml b/tests/fixtures/osv/introduced-and-fixed.yaml
new file mode 100644
index 0000000..729e203
--- /dev/null
+++ b/tests/fixtures/osv/introduced-and-fixed.yaml
@@ -0,0 +1,19 @@
+id: PYSEC-0000-0
+package:
+  name: package
+  ecosystem: PyPI
+details: Too much cheese in the cheeseshop!
+affects:
+  ranges:
+  - type: ECOSYSTEM
+    introduced: 1.0.0
+    fixed: 1.1.0
+aliases:
+- CVE-0000-12345
+modified: '2020-01-01T00:00:00Z'
+published: '2020-01-01T00:00:00Z'
+references:
+- type: WEB
+  url: https://www.pypi.org
+- type: WEB
+  url: https://www.python.org
diff --git a/tests/fixtures/osv/introduced-and-versions.yaml b/tests/fixtures/osv/introduced-and-versions.yaml
new file mode 100644
index 0000000..f303edd
--- /dev/null
+++ b/tests/fixtures/osv/introduced-and-versions.yaml
@@ -0,0 +1,23 @@
+id: PYSEC-0000-0
+package:
+  name: package
+  ecosystem: PyPI
+details: Too much cheese in the cheeseshop!
+affects:
+  ranges:
+  - type: ECOSYSTEM
+    introduced: 2.8.0
+  versions:
+      - 2.8.0
+      - 2.8.1
+      - 2.8.2
+      - 2.8.3
+aliases:
+- CVE-0000-12345
+modified: '2020-01-01T00:00:00Z'
+published: '2020-01-01T00:00:00Z'
+references:
+- type: WEB
+  url: https://www.pypi.org
+- type: WEB
+  url: https://www.python.org
diff --git a/tests/fixtures/osv/introduced-only.yaml b/tests/fixtures/osv/introduced-only.yaml
new file mode 100644
index 0000000..0412d6e
--- /dev/null
+++ b/tests/fixtures/osv/introduced-only.yaml
@@ -0,0 +1,18 @@
+id: PYSEC-0000-0
+package:
+  name: package
+  ecosystem: PyPI
+details: Too much cheese in the cheeseshop!
+affects:
+  ranges:
+  - type: ECOSYSTEM
+    introduced: 1.0.0
+aliases:
+- CVE-0000-12345
+modified: '2020-01-01T00:00:00Z'
+published: '2020-01-01T00:00:00Z'
+references:
+- type: WEB
+  url: https://www.pypi.org
+- type: WEB
+  url: https://www.python.org
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 6c25564..cbf5c8c 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import json
 import os
 from typing import Generator
diff --git a/tests/test_cvss.py b/tests/test_cvss.py
index 368e9d0..c161e99 100644
--- a/tests/test_cvss.py
+++ b/tests/test_cvss.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import pytest
 
 from skjold.cvss import parse_cvss, CVSS2, CVSS3
diff --git a/tests/test_formats.py b/tests/test_formats.py
index fc017f8..0bb39a4 100644
--- a/tests/test_formats.py
+++ b/tests/test_formats.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import io
 import os
 from typing import Optional
diff --git a/tests/test_gemnasium.py b/tests/test_gemnasium.py
index d834f51..d1102a8 100644
--- a/tests/test_gemnasium.py
+++ b/tests/test_gemnasium.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import os
 import pytest
 from typing import Any
diff --git a/tests/test_github.py b/tests/test_github.py
index a656db2..ad795f5 100644
--- a/tests/test_github.py
+++ b/tests/test_github.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import pytest
 from typing import Dict, Union, Any
 
diff --git a/tests/test_osv.py b/tests/test_osv.py
new file mode 100644
index 0000000..5546dd8
--- /dev/null
+++ b/tests/test_osv.py
@@ -0,0 +1,191 @@
+import os
+from typing import Any
+
+import pytest
+import yaml
+
+from skjold.sources.osv import OSV, OSVSecurityAdvisory, _osv_dev_api_request
+
+
+def osv_advisory_yml(name: str) -> Any:
+    _path = os.path.join(os.path.dirname(__file__), "fixtures", "osv", name)
+    assert _path.endswith(".yml") or _path.endswith(".yaml")
+    assert os.path.exists(_path)
+
+    with open(_path, "rb") as fh:
+        doc = yaml.safe_load(fh)
+
+    return doc
+
+
+def test_osv_advisory_with_introduced_and_fixed() -> None:
+    obj = OSVSecurityAdvisory.using(osv_advisory_yml("introduced-and-fixed.yaml"))
+
+    assert obj.package_name == "package"
+    assert obj.identifier == "PYSEC-0000-0"
+    assert obj.source == "osv"
+    assert obj.severity == "UNKNOWN"
+    assert obj.url == "https://www.pypi.org"
+    assert obj.references == ["https://www.pypi.org", "https://www.python.org"]
+    assert obj.vulnerable_versions == ">=1.0.0,<1.1.0"
+    assert obj.summary == "Too much cheese in the cheeseshop!"
+
+    assert obj.is_affected("1.0.0")
+    assert obj.is_affected("1.0.20")
+    assert not obj.is_affected("0.9.0")
+    assert not obj.is_affected("1.1.0")
+    assert not obj.is_affected("2.0.0")
+
+
+def test_osv_advisory_with_introduced_and_versions() -> None:
+    obj = OSVSecurityAdvisory.using(osv_advisory_yml("introduced-and-versions.yaml"))
+
+    assert obj.package_name == "package"
+    assert obj.identifier == "PYSEC-0000-0"
+    assert obj.source == "osv"
+    assert obj.severity == "UNKNOWN"
+    assert obj.url == "https://www.pypi.org"
+    assert obj.references == ["https://www.pypi.org", "https://www.python.org"]
+    assert obj.vulnerable_versions == ">=2.8.0"
+    assert obj.summary == "Too much cheese in the cheeseshop!"
+
+    assert not obj.is_affected("1.7.9")
+    assert not obj.is_affected("2.7.9")
+
+    assert obj.is_affected("2.8.0")
+    assert obj.is_affected("2.8.1")
+    assert obj.is_affected("2.8.2")
+    assert obj.is_affected("2.8.3")
+
+    assert obj.is_affected("2.8.4")
+    assert obj.is_affected("3.0.0")
+
+
+def test_osv_advisory_with_introduced_only() -> None:
+    obj = OSVSecurityAdvisory.using(osv_advisory_yml("introduced-only.yaml"))
+
+    assert obj.package_name == "package"
+    assert obj.identifier == "PYSEC-0000-0"
+    assert obj.source == "osv"
+    assert obj.severity == "UNKNOWN"
+    assert obj.url == "https://www.pypi.org"
+    assert obj.references == ["https://www.pypi.org", "https://www.python.org"]
+    assert obj.vulnerable_versions == ">=1.0.0"
+    assert obj.summary == "Too much cheese in the cheeseshop!"
+
+    assert not obj.is_affected("0.9")
+    assert not obj.is_affected("0.9.0")
+
+    assert obj.is_affected("1.0.0")
+    assert obj.is_affected("1.0")
+    assert obj.is_affected("1.2")
+    assert obj.is_affected("2.0")
+    assert obj.is_affected("2.0.0")
+
+
+def test_ensure_osv_advisory_from_yaml_with_no_cvss_vector() -> None:
+    obj = OSVSecurityAdvisory.using(osv_advisory_yml("PYSEC-2021-54.yaml"))
+
+    assert obj.package_name == "salt"
+    assert obj.identifier == "PYSEC-2021-54"
+    assert obj.source == "osv"
+    assert obj.severity == "UNKNOWN"
+    assert obj.url == "https://github.com/saltstack/salt/releases"
+    assert obj.references == [
+        "https://github.com/saltstack/salt/releases",
+        "https://saltproject.io/security_announcements/active-saltstack-cve-release-2021-feb-25/",
+        "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YOGNT2XWPOYV7YT75DN7PS4GIYWFKOK5/",
+        "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/7GRVZ5WAEI3XFN2BDTL6DDXFS5HYSDVB/",
+        "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/FUGLOJ6NXLCIFRD2JTXBYQEMAEF2B6XH/",
+        "https://security.gentoo.org/glsa/202103-01",
+    ]
+    assert (
+        obj.vulnerable_versions
+        == "<2015.8.10||>=2015.8.11,<2015.8.13||>=2016.3.0,<2016.3.4||>=2016.3.5,<2016.3.6||>=2016.3.7,<2016.3.8||>=2016.11.0,<2016.11.3||>=2016.11.4,<2016.11.5||>=2016.11.7,<2016.11.10||>=2017.7.0,<2017.7.8||>=2018.3.0rc1,<2019.2.0rc1||>=2019.2.0,<2019.2.5||>=2019.2.6,<2019.2.8||>=3000,<3000.6||>=3001,<3001.4||>=3002,<3002.5"
+    )
+    assert obj.summary.startswith("In SaltStack Salt before 3002.5, eauth tokens")
+
+
+@pytest.mark.parametrize(
+    "package_name, package_version, is_vulnerable",
+    [
+        ("package", "1.11.26", True),
+        ("package", "0.11.26", True),
+        ("package", "0.1.6", True),
+        ("package", "2.2.8", True),
+        ("package", "2.2.9", False),
+        ("package", "3.0.0", True),
+        ("package", "3.0.1", False),
+        ("package", "3.2", False),
+        ("package", "4", False),
+    ],
+)
+@pytest.mark.parametrize(
+    "doc",
+    [
+        (
+            {
+                "affects": {
+                    "versions": ["1.11.26", "0.11.26", "0.1.6", "2.2.8", "3.0"]
+                },
+                "package": {"name": "package"},
+            }
+        )
+    ],
+)
+def test_ensure_is_affected(
+    doc: Any, package_name: str, package_version: str, is_vulnerable: bool
+) -> None:
+    obj = OSVSecurityAdvisory.using(doc)
+    assert obj.package_name == "package"
+    assert len(obj.vulnerable_version_range) == len(doc["affects"]["versions"])
+    assert obj.is_affected(package_version) is is_vulnerable
+
+
+@pytest.mark.parametrize(
+    "package_version", ["1.11.26", "0.1.6", "2.2.8", "2.2.9rc2", "3.0.0", "3.2", "4"]
+)
+def test_osv_advisory_ensure_marked_affected_by_default(package_version: str) -> None:
+    obj = OSVSecurityAdvisory.using({"package": {"name": "package"}})
+    assert obj.package_name == "package"
+    assert obj.is_affected(package_version)
+
+
+def test_osv_advisory_with_vulnerable_package_via_osv_api() -> None:
+    vulnerabilities = _osv_dev_api_request("jinja2", "2.11.2")
+    assert vulnerabilities[0]
+
+    obj = OSVSecurityAdvisory.using(vulnerabilities[0])
+    assert obj.identifier == "PYSEC-2021-66"
+    assert obj.package_name == "jinja2"
+    assert obj.summary.startswith(
+        "This affects the package jinja2 from 0.0.0 and before 2.11.3."
+    )
+
+    assert obj.is_affected("0.0.0")
+    assert obj.is_affected("2.11.2")
+    assert not obj.is_affected("2.11.3")
+
+
+def test_ensure_pypi_advisory_db_update(cache_dir: str) -> None:
+    source = OSV(cache_dir, 3600)
+    assert source.name == "osv"
+
+    assert len(source._advisories) == 0
+    _ = source.advisories
+    assert source.total_count == 0
+    assert len(source._advisories) == 0
+
+    assert source.has_security_advisory_for("ansible")
+
+    found, findings = source.is_vulnerable_package("doesnotexist", "1.0.0")
+    assert found is False and len(findings) == 0
+
+    found, findings = source.is_vulnerable_package("ansible", "2.8.1")
+    assert found and len(findings) > 0
+
+    found, findings = source.is_vulnerable_package("ansible", "2.8.3")
+    assert found and len(findings) > 0
+
+    found, findings = source.is_vulnerable_package("ansible", "4.1.0")
+    assert found is False and len(findings) == 0
diff --git a/tests/test_pypa.py b/tests/test_pypa.py
new file mode 100644
index 0000000..991ac31
--- /dev/null
+++ b/tests/test_pypa.py
@@ -0,0 +1,25 @@
+from skjold.sources.pypa import PyPAAdvisoryDB
+
+
+def test_ensure_pypi_advisory_db_update(cache_dir: str) -> None:
+    source = PyPAAdvisoryDB(cache_dir, 3600)
+    assert source.name == "pypa"
+    assert len(source._advisories) == 0
+
+    _ = source.advisories
+    assert len(source._advisories) > 0
+    assert source.total_count > 100
+
+    assert source.has_security_advisory_for("ansible")
+
+    found, findings = source.is_vulnerable_package("doesnotexist", "1.0.0")
+    assert found is False and len(findings) == 0
+
+    found, findings = source.is_vulnerable_package("ansible", "2.8.1")
+    assert found and len(findings) > 0
+
+    found, findings = source.is_vulnerable_package("ansible", "2.8.3")
+    assert found and len(findings) > 0
+
+    found, findings = source.is_vulnerable_package("ansible", "4.1.0")
+    assert found is False and len(findings) == 0
diff --git a/tests/test_pyup.py b/tests/test_pyup.py
index a593086..3ec9a66 100644
--- a/tests/test_pyup.py
+++ b/tests/test_pyup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import datetime
 import json
 from typing import List, Dict, Any
diff --git a/tests/test_sources.py b/tests/test_sources.py
index bad681d..3ecb96d 100644
--- a/tests/test_sources.py
+++ b/tests/test_sources.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import os
 from typing import List, Any, Tuple