├── .bandit ├── .checks.yml ├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── depx ├── __init__.py ├── cli.py ├── depx.py ├── graph.py ├── parsing.py └── template.html ├── docs ├── Makefile ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── poetry.lock ├── pyproject.toml ├── setup.cfg ├── tests ├── __init__.py ├── fake_project │ ├── another_fake_module │ │ ├── __init__.py │ │ └── missing_creativity.py │ ├── fake_module │ │ ├── __init__.py │ │ ├── a_module.py │ │ └── another_module.py │ ├── random_folder │ │ ├── cool_file.txt │ │ └── python_file_without_py_extension.txt │ └── setup.py ├── test_depx.py ├── test_graph.py └── test_parsing.py └── tox.ini /.bandit: -------------------------------------------------------------------------------- 1 | [bandit] 2 | exclude: test 3 | -------------------------------------------------------------------------------- /.checks.yml: -------------------------------------------------------------------------------- 1 | - bandit 2 | - flake8 3 | - pydocstyle 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Depx version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # Jupyter Notebook 57 | .ipynb_checkpoints 58 | 59 | # pyenv 60 | .python-version 61 | 62 | # dotenv 63 | .env 64 | 65 | # virtualenv 66 | .venv 67 | venv/ 68 | ENV/ 69 | 70 | # mkdocs documentation 71 | /site 72 | 73 | # mypy 74 | .mypy_cache/ 75 | 76 | # IDEs 77 | .idea/ 78 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | python: 4 | - 3.9 5 | - 3.8 6 | - 3.7 7 | - 3.6 8 | 9 | install: pip install -U tox-travis 10 | 11 | script: tox 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2019 Thermondo GmbH 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | 19 | Also add information on how to contact you by electronic and paper mail. 20 | 21 | You should also get your employer (if you work as a programmer) or school, 22 | if any, to sign a "copyright disclaimer" for the program, if necessary. 23 | For more information on this, and how to apply and follow the GNU GPL, see 24 | . 25 | 26 | The GNU General Public License does not permit incorporating your program 27 | into proprietary programs. If your program is a subroutine library, you 28 | may consider it more useful to permit linking proprietary applications with 29 | the library. If this is what you want to do, use the GNU Lesser General 30 | Public License instead of this License. But first, please read 31 | . 32 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.rst 2 | include HISTORY.rst 3 | include LICENSE 4 | include README.rst 5 | 6 | recursive-include tests * 7 | recursive-exclude * __pycache__ 8 | recursive-exclude * *.py[co] 9 | 10 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help requirements 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | poetry run flake8 depx tests 55 | 56 | test: ## run tests quickly with the default Python 57 | poetry run pytest 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | coverage: ## check code coverage quickly with the default Python 63 | poetry run coverage run --source depx -m pytest 64 | poetry run coverage report -m 65 | poetry run coverage html 66 | $(BROWSER) htmlcov/index.html 67 | 68 | docs: ## generate Sphinx HTML documentation, including API docs 69 | rm -f docs/depx.rst 70 | rm -f docs/modules.rst 71 | poetry run sphinx-apidoc -o docs/ depx 72 | $(MAKE) -C docs clean 73 | $(MAKE) -C docs html 74 | $(BROWSER) docs/_build/html/index.html 75 | 76 | servedocs: docs ## compile the docs watching for changes 77 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Depx 2 | 3 | [![Build Status](https://api.travis-ci.org/tilboerner/depx.svg?branch=master)](https://travis-ci.org/tilboerner/depx) 4 | 5 | 6 | Examine and visualize dependencies used by Python modules. 7 | 8 | This is a simple, exploratory prototype for now. The goal is to use explicit 9 | `import` statements in a module to identify the names of its dependencies. This 10 | will not always produce a complete picture of all other code a module uses, 11 | since there is a multitude of ways to import or reference other code 12 | dynamically. Idiomatic Django, for example, uses a number of them. Those are 13 | all out of scope, however, although some might get considered at a later time. 14 | 15 | In this early stage, all functionality, structure and interfaces are subject to 16 | radical and surprising changes. 17 | 18 | 19 | ## Dev setup 20 | 21 | You're going to need [Poetry](https://python-poetry.org/) and 22 | [tox](http://tox.readthedocs.org/). 23 | 24 | After cloning the repo, set up the dev environment: 25 | 26 | $ cd depx 27 | $ poetry install --no-root 28 | 29 | Run tests: 30 | 31 | $ make test 32 | 33 | Lint: 34 | 35 | $ make lint 36 | 37 | Comprehensive test suite: 38 | 39 | $ tox 40 | 41 | Add dependencies: 42 | 43 | $ poetry add [--dev] 44 | 45 | 46 | ## Envisioned use cases 47 | 48 | The project should enable the following use cases, but might do so by handing 49 | off the right kind of data to other software. If we do different things 50 | ourselves, they should end up in different commands (or at least subcommands). 51 | 52 | * visualize dependencies of module, package or project 53 | * identify dependency cycles 54 | * help identify unused dependencies 55 | 56 | 57 | ## Existing and potential functionality 58 | 59 | Sketch of ideas. This is not meant as a plan, but to guide initial development. 60 | 61 | ### Find dependencies 62 | 63 | - [x] identify all explicit imports a module makes 64 | - [x] same for whole packages 65 | - [x] same for arbitrary directories 66 | - [x] identify local imports (those are smelly) 67 | - [ ] distinguish built-in, 3rd party and local dependencies 68 | 69 | 70 | ### Names 71 | 72 | - [x] show fully qualified names for importing module and dependency 73 | - [x] ability to show only top-level names 74 | - [x] resolve relative imports to proper names 75 | - [ ] resolve `*` imports 76 | 77 | 78 | ### Output formats 79 | 80 | depx my-awesome-project --format html 81 | 82 | The identified dependencies are the edges of a directed graph. Output formats 83 | should include several standard ways to consume such data. 84 | 85 | - [x] JSON 86 | - [x] GraphML 87 | - [x] browser-ready HTML with visualization 88 | - [x] Graphviz (`.dot`) 89 | - [ ] text with columns (to compose with Unix pipes) 90 | -------------------------------------------------------------------------------- /depx/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Top-level package for Depx.""" 4 | 5 | __author__ = """Til Boerner""" 6 | __email__ = 'github@dxdm.org' 7 | __version__ = '0.2.0' 8 | -------------------------------------------------------------------------------- /depx/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | from depx.graph import create_graph_from, to_json, to_html, to_graphml, to_dotfile 3 | from depx.parsing import find_imports, filter_top_level_names 4 | import sys 5 | 6 | 7 | formatters = { 8 | 'json': to_json, 9 | 'html': to_html, 10 | 'graphml': to_graphml, 11 | 'dot': to_dotfile, 12 | } 13 | 14 | 15 | @click.command() 16 | @click.argument('path') 17 | @click.option( 18 | '--format', 19 | '-f', 20 | type=click.Choice(formatters), 21 | default='json', 22 | help='Graph output format.', 23 | ) 24 | @click.option( 25 | '--short-names/--no-short-names', 26 | '-s/ ', 27 | default=False, 28 | help='Use the top-level name only for all dependencies (up to first).', 29 | ) 30 | def main(path, **kwargs): 31 | to_format = kwargs['format'] 32 | short_names = kwargs['short_names'] 33 | deps = list(find_imports(path)) 34 | 35 | if short_names: 36 | deps = filter_top_level_names(deps) 37 | deps = list(deps) 38 | 39 | if deps: 40 | graph = create_graph_from(deps) 41 | 42 | export_to = formatters.get(to_format) 43 | click.echo(export_to(graph=graph, path=path, dependencies=deps)) 44 | 45 | return 0 46 | 47 | 48 | if __name__ == '__main__': 49 | sys.exit(main()) # pragma: no cover 50 | -------------------------------------------------------------------------------- /depx/depx.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Main module.""" 4 | -------------------------------------------------------------------------------- /depx/graph.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Template 2 | import json 3 | import networkx as nx 4 | from networkx.readwrite import json_graph 5 | from networkx.drawing.nx_pydot import to_pydot 6 | import io 7 | from pathlib import Path 8 | 9 | 10 | TEMPLATE_FILE = str(Path(__file__).parent.absolute() / 'template.html') 11 | 12 | 13 | def create_graph_from(dependencies): 14 | graph = nx.DiGraph() 15 | 16 | for dependency in dependencies: 17 | try: 18 | weight = graph[dependency['from_module']][dependency['to_module']]['weight'] 19 | weight += 1 20 | graph[dependency['from_module']][dependency['to_module']]['weight'] = weight 21 | except KeyError: 22 | graph.add_edge(dependency['from_module'], dependency['to_module'], weight=1) 23 | return graph 24 | 25 | 26 | def to_html(**kwargs): 27 | data = json_graph.node_link_data(kwargs['graph']) 28 | 29 | with open(TEMPLATE_FILE) as file_: 30 | template = Template(file_.read()) 31 | content = template.render(data=data, path=kwargs['path']) 32 | return content 33 | 34 | 35 | def to_graphml(**kwargs): 36 | in_memory_file = io.BytesIO() 37 | 38 | nx.write_graphml(kwargs['graph'], in_memory_file) 39 | return in_memory_file.getvalue().decode('utf-8') 40 | 41 | 42 | def to_dotfile(**kwargs): 43 | pydot = to_pydot(kwargs['graph']) 44 | return pydot.to_string() 45 | 46 | 47 | def to_json(**kwargs): 48 | return json.dumps(kwargs['dependencies'], indent=4) 49 | -------------------------------------------------------------------------------- /depx/parsing.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import logging 3 | import os 4 | import re 5 | 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def _dependency( 11 | *, from_module, to_module, category='', is_relative=False, level=0, **kwargs 12 | ): 13 | dep = { 14 | 'from_module': from_module, 15 | 'to_module': to_module, # currently, this could also be a symbol within a module 16 | 'category': category, 17 | 'is_relative': is_relative, 18 | 'level': level, 19 | } 20 | dep.update(kwargs) 21 | return dep 22 | 23 | 24 | def _goes_local(node): 25 | func_defs = (ast.FunctionDef,) 26 | try: 27 | func_defs += (ast.AsyncFunctionDef,) 28 | except AttributeError: # Py < 3.5 29 | pass 30 | return isinstance(node, func_defs) 31 | 32 | 33 | def _walk(node): 34 | from collections import deque 35 | 36 | iter_child_nodes = ast.iter_child_nodes 37 | todo = deque([(node, False)]) 38 | while todo: 39 | node, is_local = todo.popleft() 40 | child_local = is_local or _goes_local(node) 41 | todo.extend((child, child_local) for child in iter_child_nodes(node)) 42 | yield node, is_local 43 | 44 | 45 | def _is_package(path): 46 | init_path = os.path.join(path, '__init__.py') 47 | return os.path.isfile(init_path) 48 | 49 | 50 | def _find_base_name(path, base_name=''): 51 | parent = os.path.dirname(path) 52 | if not _is_package(parent): 53 | return base_name 54 | parent_name = os.path.basename(parent) 55 | if base_name: 56 | base_name = parent_name + '.' + base_name 57 | else: 58 | base_name = parent_name 59 | return _find_base_name(parent, base_name) 60 | 61 | 62 | def _is_module(path): 63 | if not os.path.isfile(path): 64 | return False 65 | if path.endswith('.py'): 66 | return True 67 | with open(path) as f: 68 | try: 69 | line = f.readline() 70 | except UnicodeError: 71 | return False 72 | return bool(line and re.match(r'^#!.*?python', line)) 73 | 74 | 75 | def find_imports(path): 76 | if _is_module(path): 77 | yield from find_module_imports(path) 78 | else: 79 | yield from find_imports_in_directory(path) 80 | 81 | 82 | def find_imports_in_directory(directory_path): 83 | for path, subdirs, files in os.walk(directory_path): 84 | subdirs[:] = [d for d in subdirs if _is_package(os.path.join(path, d))] 85 | for filename in files: 86 | module_path = os.path.join(path, filename) 87 | if not _is_module(module_path): 88 | continue 89 | yield from find_module_imports(module_path) 90 | 91 | 92 | def find_module_imports(path, base_name=None): 93 | with open(path) as f: 94 | text = f.read() 95 | module_name = os.path.splitext(os.path.basename(path))[0] 96 | if base_name is None: 97 | base_name = _find_base_name(path) 98 | if base_name: 99 | module_name = base_name + '.' + module_name 100 | return find_imports_from_text(text, module_name) 101 | 102 | 103 | def find_imports_from_text(text, base_name): 104 | tree = ast.parse(text) 105 | for node, is_local in _walk(tree): 106 | category = 'local' if is_local else 'module' 107 | if isinstance(node, ast.Import): 108 | for alias in node.names: 109 | yield _dependency( 110 | category=category, 111 | from_module=base_name, 112 | from_name=alias.asname or alias.name, 113 | to_module=alias.name, 114 | to_name=alias.name, 115 | ) 116 | elif isinstance(node, ast.ImportFrom): 117 | level = node.level 118 | module = node.module 119 | for alias in node.names: 120 | if level: 121 | to_module = ( 122 | module or alias.name 123 | ) # module is None for 'from . import' 124 | to_module = _resolve_relative_name(to_module, base_name, level) 125 | else: 126 | to_module = module 127 | yield _dependency( 128 | category=category, 129 | from_module=base_name, 130 | from_name=alias.asname or alias.name, 131 | to_module=to_module, 132 | to_name=alias.name, 133 | is_relative=bool(level), 134 | level=level, 135 | ) 136 | 137 | 138 | def filter_top_level_names(deps): 139 | for dep in deps: 140 | dep['from_module'] = dep['from_module'].split('.')[0] 141 | dep['to_module'] = dep['to_module'].split('.')[0] 142 | dep['from_name'] = '' 143 | dep['to_name'] = '' 144 | yield dep 145 | 146 | 147 | def _resolve_relative_name(name, base_name, level): 148 | parts = base_name and base_name.split('.') 149 | if not (parts and all(parts)) or len(parts) < level: 150 | logger.warning( 151 | 'Unable to resolve relative name %r: bad or missing basename (%r)', 152 | name, 153 | base_name, 154 | ) 155 | return name 156 | resolved_base = parts[:-level] 157 | return '.'.join(resolved_base + [name]) 158 | 159 | 160 | if __name__ == '__main__': 161 | from pprint import pprint 162 | 163 | for x in find_module_imports(__file__): 164 | pprint(x) 165 | -------------------------------------------------------------------------------- /depx/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Depx 6 | 7 | 8 | 9 | 10 |

Depx

11 |

Path: {{ path }}

12 | 13 | 29 | 30 | 31 | 113 | 114 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = poetry run python -msphinx 7 | SPHINXPROJ = depx 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # depx documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another 17 | # directory, add these directories to sys.path here. If the directory is 18 | # relative to the documentation root, use os.path.abspath to make it 19 | # absolute, like shown here. 20 | # 21 | import os 22 | import sys 23 | 24 | sys.path.insert(0, os.path.abspath('..')) 25 | 26 | import depx 27 | 28 | # -- General configuration --------------------------------------------- 29 | 30 | # If your documentation needs a minimal Sphinx version, state it here. 31 | # 32 | # needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 36 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'Depx' 52 | copyright = u"2019, Thermondo" 53 | author = u"Til Boerner" 54 | 55 | # The version info for the project you're documenting, acts as replacement 56 | # for |version| and |release|, also used in various other places throughout 57 | # the built documents. 58 | # 59 | # The short X.Y version. 60 | version = depx.__version__ 61 | # The full version, including alpha/beta/rc tags. 62 | release = depx.__version__ 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This patterns also effect to html_static_path and html_extra_path 74 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = 'sphinx' 78 | 79 | # If true, `todo` and `todoList` produce output, else they produce nothing. 80 | todo_include_todos = False 81 | 82 | 83 | # -- Options for HTML output ------------------------------------------- 84 | 85 | # The theme to use for HTML and HTML Help pages. See the documentation for 86 | # a list of builtin themes. 87 | # 88 | html_theme = 'alabaster' 89 | 90 | # Theme options are theme-specific and customize the look and feel of a 91 | # theme further. For a list of options available for each theme, see the 92 | # documentation. 93 | # 94 | # html_theme_options = {} 95 | 96 | # Add any paths that contain custom static files (such as style sheets) here, 97 | # relative to this directory. They are copied after the builtin static files, 98 | # so a file named "default.css" will overwrite the builtin "default.css". 99 | html_static_path = ['_static'] 100 | 101 | 102 | # -- Options for HTMLHelp output --------------------------------------- 103 | 104 | # Output file base name for HTML help builder. 105 | htmlhelp_basename = 'depxdoc' 106 | 107 | 108 | # -- Options for LaTeX output ------------------------------------------ 109 | 110 | latex_elements = { 111 | # The paper size ('letterpaper' or 'a4paper'). 112 | # 113 | # 'papersize': 'letterpaper', 114 | # The font size ('10pt', '11pt' or '12pt'). 115 | # 116 | # 'pointsize': '10pt', 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | # Latex figure (float) alignment 121 | # 122 | # 'figure_align': 'htbp', 123 | } 124 | 125 | # Grouping the document tree into LaTeX files. List of tuples 126 | # (source start file, target name, title, author, documentclass 127 | # [howto, manual, or own class]). 128 | latex_documents = [ 129 | (master_doc, 'depx.tex', u'Depx Documentation', u'Til Boerner', 'manual'), 130 | ] 131 | 132 | 133 | # -- Options for manual page output ------------------------------------ 134 | 135 | # One entry per manual page. List of tuples 136 | # (source start file, name, description, authors, manual section). 137 | man_pages = [(master_doc, 'depx', u'Depx Documentation', [author], 1)] 138 | 139 | 140 | # -- Options for Texinfo output ---------------------------------------- 141 | 142 | # Grouping the document tree into Texinfo files. List of tuples 143 | # (source start file, target name, title, author, 144 | # dir menu entry, description, category) 145 | texinfo_documents = [ 146 | ( 147 | master_doc, 148 | 'depx', 149 | u'Depx Documentation', 150 | author, 151 | 'depx', 152 | 'One line description of project.', 153 | 'Miscellaneous', 154 | ), 155 | ] 156 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Depx's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | history 14 | 15 | Indices and tables 16 | ================== 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install Depx, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install depx 16 | 17 | This is the preferred method to install Depx, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=depx 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use Depx in a project:: 6 | 7 | import depx 8 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "alabaster" 3 | version = "0.7.12" 4 | description = "A configurable sidebar-enabled Sphinx theme" 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "appdirs" 11 | version = "1.4.4" 12 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 13 | category = "dev" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [[package]] 18 | name = "atomicwrites" 19 | version = "1.4.0" 20 | description = "Atomic file writes." 21 | category = "dev" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | 25 | [[package]] 26 | name = "attrs" 27 | version = "20.3.0" 28 | description = "Classes Without Boilerplate" 29 | category = "dev" 30 | optional = false 31 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 32 | 33 | [package.extras] 34 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 35 | docs = ["furo", "sphinx", "zope.interface"] 36 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 37 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 38 | 39 | [[package]] 40 | name = "babel" 41 | version = "2.8.0" 42 | description = "Internationalization utilities" 43 | category = "dev" 44 | optional = false 45 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 46 | 47 | [package.dependencies] 48 | pytz = ">=2015.7" 49 | 50 | [[package]] 51 | name = "black" 52 | version = "20.8b1" 53 | description = "The uncompromising code formatter." 54 | category = "dev" 55 | optional = false 56 | python-versions = ">=3.6" 57 | 58 | [package.dependencies] 59 | appdirs = "*" 60 | click = ">=7.1.2" 61 | dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} 62 | mypy-extensions = ">=0.4.3" 63 | pathspec = ">=0.6,<1" 64 | regex = ">=2020.1.8" 65 | toml = ">=0.10.1" 66 | typed-ast = ">=1.4.0" 67 | typing-extensions = ">=3.7.4" 68 | 69 | [package.extras] 70 | colorama = ["colorama (>=0.4.3)"] 71 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 72 | 73 | [[package]] 74 | name = "certifi" 75 | version = "2020.11.8" 76 | description = "Python package for providing Mozilla's CA Bundle." 77 | category = "dev" 78 | optional = false 79 | python-versions = "*" 80 | 81 | [[package]] 82 | name = "chardet" 83 | version = "3.0.4" 84 | description = "Universal encoding detector for Python 2 and 3" 85 | category = "dev" 86 | optional = false 87 | python-versions = "*" 88 | 89 | [[package]] 90 | name = "click" 91 | version = "7.1.2" 92 | description = "Composable command line interface toolkit" 93 | category = "main" 94 | optional = false 95 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 96 | 97 | [[package]] 98 | name = "colorama" 99 | version = "0.4.4" 100 | description = "Cross-platform colored terminal text." 101 | category = "dev" 102 | optional = false 103 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 104 | 105 | [[package]] 106 | name = "coverage" 107 | version = "5.3" 108 | description = "Code coverage measurement for Python" 109 | category = "dev" 110 | optional = false 111 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 112 | 113 | [package.extras] 114 | toml = ["toml"] 115 | 116 | [[package]] 117 | name = "dataclasses" 118 | version = "0.7" 119 | description = "A backport of the dataclasses module for Python 3.6" 120 | category = "dev" 121 | optional = false 122 | python-versions = ">=3.6, <3.7" 123 | 124 | [[package]] 125 | name = "decorator" 126 | version = "4.4.2" 127 | description = "Decorators for Humans" 128 | category = "main" 129 | optional = false 130 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 131 | 132 | [[package]] 133 | name = "docutils" 134 | version = "0.16" 135 | description = "Docutils -- Python Documentation Utilities" 136 | category = "dev" 137 | optional = false 138 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 139 | 140 | [[package]] 141 | name = "flake8" 142 | version = "3.8.4" 143 | description = "the modular source code checker: pep8 pyflakes and co" 144 | category = "dev" 145 | optional = false 146 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 147 | 148 | [package.dependencies] 149 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 150 | mccabe = ">=0.6.0,<0.7.0" 151 | pycodestyle = ">=2.6.0a1,<2.7.0" 152 | pyflakes = ">=2.2.0,<2.3.0" 153 | 154 | [[package]] 155 | name = "idna" 156 | version = "2.10" 157 | description = "Internationalized Domain Names in Applications (IDNA)" 158 | category = "dev" 159 | optional = false 160 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 161 | 162 | [[package]] 163 | name = "imagesize" 164 | version = "1.2.0" 165 | description = "Getting image size from png/jpeg/jpeg2000/gif file" 166 | category = "dev" 167 | optional = false 168 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 169 | 170 | [[package]] 171 | name = "importlib-metadata" 172 | version = "2.0.0" 173 | description = "Read metadata from Python packages" 174 | category = "dev" 175 | optional = false 176 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 177 | 178 | [package.dependencies] 179 | zipp = ">=0.5" 180 | 181 | [package.extras] 182 | docs = ["sphinx", "rst.linker"] 183 | testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] 184 | 185 | [[package]] 186 | name = "iniconfig" 187 | version = "1.1.1" 188 | description = "iniconfig: brain-dead simple config-ini parsing" 189 | category = "dev" 190 | optional = false 191 | python-versions = "*" 192 | 193 | [[package]] 194 | name = "jinja2" 195 | version = "2.11.2" 196 | description = "A very fast and expressive template engine." 197 | category = "main" 198 | optional = false 199 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 200 | 201 | [package.dependencies] 202 | MarkupSafe = ">=0.23" 203 | 204 | [package.extras] 205 | i18n = ["Babel (>=0.8)"] 206 | 207 | [[package]] 208 | name = "markupsafe" 209 | version = "1.1.1" 210 | description = "Safely add untrusted strings to HTML/XML markup." 211 | category = "main" 212 | optional = false 213 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 214 | 215 | [[package]] 216 | name = "mccabe" 217 | version = "0.6.1" 218 | description = "McCabe checker, plugin for flake8" 219 | category = "dev" 220 | optional = false 221 | python-versions = "*" 222 | 223 | [[package]] 224 | name = "mypy-extensions" 225 | version = "0.4.3" 226 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 227 | category = "dev" 228 | optional = false 229 | python-versions = "*" 230 | 231 | [[package]] 232 | name = "networkx" 233 | version = "2.5" 234 | description = "Python package for creating and manipulating graphs and networks" 235 | category = "main" 236 | optional = false 237 | python-versions = ">=3.6" 238 | 239 | [package.dependencies] 240 | decorator = ">=4.3.0" 241 | 242 | [package.extras] 243 | all = ["numpy", "scipy", "pandas", "matplotlib", "pygraphviz", "pydot", "pyyaml", "lxml", "pytest"] 244 | gdal = ["gdal"] 245 | lxml = ["lxml"] 246 | matplotlib = ["matplotlib"] 247 | numpy = ["numpy"] 248 | pandas = ["pandas"] 249 | pydot = ["pydot"] 250 | pygraphviz = ["pygraphviz"] 251 | pytest = ["pytest"] 252 | pyyaml = ["pyyaml"] 253 | scipy = ["scipy"] 254 | 255 | [[package]] 256 | name = "packaging" 257 | version = "20.4" 258 | description = "Core utilities for Python packages" 259 | category = "dev" 260 | optional = false 261 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 262 | 263 | [package.dependencies] 264 | pyparsing = ">=2.0.2" 265 | six = "*" 266 | 267 | [[package]] 268 | name = "pathspec" 269 | version = "0.8.1" 270 | description = "Utility library for gitignore style pattern matching of file paths." 271 | category = "dev" 272 | optional = false 273 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 274 | 275 | [[package]] 276 | name = "pluggy" 277 | version = "0.13.1" 278 | description = "plugin and hook calling mechanisms for python" 279 | category = "dev" 280 | optional = false 281 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 282 | 283 | [package.dependencies] 284 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 285 | 286 | [package.extras] 287 | dev = ["pre-commit", "tox"] 288 | 289 | [[package]] 290 | name = "py" 291 | version = "1.9.0" 292 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 293 | category = "dev" 294 | optional = false 295 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 296 | 297 | [[package]] 298 | name = "pycodestyle" 299 | version = "2.6.0" 300 | description = "Python style guide checker" 301 | category = "dev" 302 | optional = false 303 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 304 | 305 | [[package]] 306 | name = "pydot" 307 | version = "1.4.1" 308 | description = "Python interface to Graphviz's Dot" 309 | category = "main" 310 | optional = false 311 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 312 | 313 | [package.dependencies] 314 | pyparsing = ">=2.1.4" 315 | 316 | [[package]] 317 | name = "pyflakes" 318 | version = "2.2.0" 319 | description = "passive checker of Python programs" 320 | category = "dev" 321 | optional = false 322 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 323 | 324 | [[package]] 325 | name = "pygments" 326 | version = "2.7.2" 327 | description = "Pygments is a syntax highlighting package written in Python." 328 | category = "dev" 329 | optional = false 330 | python-versions = ">=3.5" 331 | 332 | [[package]] 333 | name = "pyparsing" 334 | version = "2.4.7" 335 | description = "Python parsing module" 336 | category = "main" 337 | optional = false 338 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 339 | 340 | [[package]] 341 | name = "pytest" 342 | version = "6.1.2" 343 | description = "pytest: simple powerful testing with Python" 344 | category = "dev" 345 | optional = false 346 | python-versions = ">=3.5" 347 | 348 | [package.dependencies] 349 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 350 | attrs = ">=17.4.0" 351 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 352 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 353 | iniconfig = "*" 354 | packaging = "*" 355 | pluggy = ">=0.12,<1.0" 356 | py = ">=1.8.2" 357 | toml = "*" 358 | 359 | [package.extras] 360 | checkqa_mypy = ["mypy (==0.780)"] 361 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 362 | 363 | [[package]] 364 | name = "pytz" 365 | version = "2020.4" 366 | description = "World timezone definitions, modern and historical" 367 | category = "dev" 368 | optional = false 369 | python-versions = "*" 370 | 371 | [[package]] 372 | name = "regex" 373 | version = "2020.10.28" 374 | description = "Alternative regular expression module, to replace re." 375 | category = "dev" 376 | optional = false 377 | python-versions = "*" 378 | 379 | [[package]] 380 | name = "requests" 381 | version = "2.24.0" 382 | description = "Python HTTP for Humans." 383 | category = "dev" 384 | optional = false 385 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 386 | 387 | [package.dependencies] 388 | certifi = ">=2017.4.17" 389 | chardet = ">=3.0.2,<4" 390 | idna = ">=2.5,<3" 391 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" 392 | 393 | [package.extras] 394 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 395 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 396 | 397 | [[package]] 398 | name = "six" 399 | version = "1.15.0" 400 | description = "Python 2 and 3 compatibility utilities" 401 | category = "dev" 402 | optional = false 403 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 404 | 405 | [[package]] 406 | name = "snowballstemmer" 407 | version = "2.0.0" 408 | description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." 409 | category = "dev" 410 | optional = false 411 | python-versions = "*" 412 | 413 | [[package]] 414 | name = "sphinx" 415 | version = "3.3.0" 416 | description = "Python documentation generator" 417 | category = "dev" 418 | optional = false 419 | python-versions = ">=3.5" 420 | 421 | [package.dependencies] 422 | alabaster = ">=0.7,<0.8" 423 | babel = ">=1.3" 424 | colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} 425 | docutils = ">=0.12" 426 | imagesize = "*" 427 | Jinja2 = ">=2.3" 428 | packaging = "*" 429 | Pygments = ">=2.0" 430 | requests = ">=2.5.0" 431 | snowballstemmer = ">=1.1" 432 | sphinxcontrib-applehelp = "*" 433 | sphinxcontrib-devhelp = "*" 434 | sphinxcontrib-htmlhelp = "*" 435 | sphinxcontrib-jsmath = "*" 436 | sphinxcontrib-qthelp = "*" 437 | sphinxcontrib-serializinghtml = "*" 438 | 439 | [package.extras] 440 | docs = ["sphinxcontrib-websupport"] 441 | lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.790)", "docutils-stubs"] 442 | test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"] 443 | 444 | [[package]] 445 | name = "sphinxcontrib-applehelp" 446 | version = "1.0.2" 447 | description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" 448 | category = "dev" 449 | optional = false 450 | python-versions = ">=3.5" 451 | 452 | [package.extras] 453 | lint = ["flake8", "mypy", "docutils-stubs"] 454 | test = ["pytest"] 455 | 456 | [[package]] 457 | name = "sphinxcontrib-devhelp" 458 | version = "1.0.2" 459 | description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." 460 | category = "dev" 461 | optional = false 462 | python-versions = ">=3.5" 463 | 464 | [package.extras] 465 | lint = ["flake8", "mypy", "docutils-stubs"] 466 | test = ["pytest"] 467 | 468 | [[package]] 469 | name = "sphinxcontrib-htmlhelp" 470 | version = "1.0.3" 471 | description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" 472 | category = "dev" 473 | optional = false 474 | python-versions = ">=3.5" 475 | 476 | [package.extras] 477 | lint = ["flake8", "mypy", "docutils-stubs"] 478 | test = ["pytest", "html5lib"] 479 | 480 | [[package]] 481 | name = "sphinxcontrib-jsmath" 482 | version = "1.0.1" 483 | description = "A sphinx extension which renders display math in HTML via JavaScript" 484 | category = "dev" 485 | optional = false 486 | python-versions = ">=3.5" 487 | 488 | [package.extras] 489 | test = ["pytest", "flake8", "mypy"] 490 | 491 | [[package]] 492 | name = "sphinxcontrib-qthelp" 493 | version = "1.0.3" 494 | description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." 495 | category = "dev" 496 | optional = false 497 | python-versions = ">=3.5" 498 | 499 | [package.extras] 500 | lint = ["flake8", "mypy", "docutils-stubs"] 501 | test = ["pytest"] 502 | 503 | [[package]] 504 | name = "sphinxcontrib-serializinghtml" 505 | version = "1.1.4" 506 | description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." 507 | category = "dev" 508 | optional = false 509 | python-versions = ">=3.5" 510 | 511 | [package.extras] 512 | lint = ["flake8", "mypy", "docutils-stubs"] 513 | test = ["pytest"] 514 | 515 | [[package]] 516 | name = "toml" 517 | version = "0.10.2" 518 | description = "Python Library for Tom's Obvious, Minimal Language" 519 | category = "dev" 520 | optional = false 521 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 522 | 523 | [[package]] 524 | name = "typed-ast" 525 | version = "1.4.1" 526 | description = "a fork of Python 2 and 3 ast modules with type comment support" 527 | category = "dev" 528 | optional = false 529 | python-versions = "*" 530 | 531 | [[package]] 532 | name = "typing-extensions" 533 | version = "3.7.4.3" 534 | description = "Backported and Experimental Type Hints for Python 3.5+" 535 | category = "dev" 536 | optional = false 537 | python-versions = "*" 538 | 539 | [[package]] 540 | name = "urllib3" 541 | version = "1.25.11" 542 | description = "HTTP library with thread-safe connection pooling, file post, and more." 543 | category = "dev" 544 | optional = false 545 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 546 | 547 | [package.extras] 548 | brotli = ["brotlipy (>=0.6.0)"] 549 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 550 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 551 | 552 | [[package]] 553 | name = "zipp" 554 | version = "3.4.0" 555 | description = "Backport of pathlib-compatible object wrapper for zip files" 556 | category = "dev" 557 | optional = false 558 | python-versions = ">=3.6" 559 | 560 | [package.extras] 561 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 562 | 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"] 563 | 564 | [metadata] 565 | lock-version = "1.1" 566 | python-versions = "^3.6" 567 | content-hash = "60ef7abbefbd7da702a5539700e590fe7c54dbde59da7d81c0af8df5e7b1de83" 568 | 569 | [metadata.files] 570 | alabaster = [ 571 | {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, 572 | {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, 573 | ] 574 | appdirs = [ 575 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 576 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 577 | ] 578 | atomicwrites = [ 579 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 580 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 581 | ] 582 | attrs = [ 583 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 584 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 585 | ] 586 | babel = [ 587 | {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, 588 | {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, 589 | ] 590 | black = [ 591 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, 592 | ] 593 | certifi = [ 594 | {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, 595 | {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, 596 | ] 597 | chardet = [ 598 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 599 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 600 | ] 601 | click = [ 602 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 603 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 604 | ] 605 | colorama = [ 606 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 607 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 608 | ] 609 | coverage = [ 610 | {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, 611 | {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, 612 | {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, 613 | {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, 614 | {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, 615 | {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, 616 | {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, 617 | {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, 618 | {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, 619 | {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, 620 | {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, 621 | {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, 622 | {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, 623 | {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, 624 | {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, 625 | {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, 626 | {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, 627 | {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, 628 | {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, 629 | {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, 630 | {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, 631 | {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, 632 | {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, 633 | {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, 634 | {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, 635 | {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, 636 | {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, 637 | {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, 638 | {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, 639 | {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, 640 | {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, 641 | {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, 642 | {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, 643 | {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, 644 | ] 645 | dataclasses = [ 646 | {file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"}, 647 | {file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"}, 648 | ] 649 | decorator = [ 650 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 651 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 652 | ] 653 | docutils = [ 654 | {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, 655 | {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, 656 | ] 657 | flake8 = [ 658 | {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, 659 | {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, 660 | ] 661 | idna = [ 662 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 663 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 664 | ] 665 | imagesize = [ 666 | {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, 667 | {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, 668 | ] 669 | importlib-metadata = [ 670 | {file = "importlib_metadata-2.0.0-py2.py3-none-any.whl", hash = "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3"}, 671 | {file = "importlib_metadata-2.0.0.tar.gz", hash = "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da"}, 672 | ] 673 | iniconfig = [ 674 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 675 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 676 | ] 677 | jinja2 = [ 678 | {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, 679 | {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, 680 | ] 681 | markupsafe = [ 682 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 683 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 684 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 685 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 686 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 687 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 688 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 689 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 690 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 691 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 692 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 693 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 694 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 695 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 696 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 697 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 698 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 699 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 700 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 701 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 702 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 703 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 704 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 705 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 706 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 707 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 708 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 709 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 710 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 711 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 712 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 713 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 714 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 715 | ] 716 | mccabe = [ 717 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 718 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 719 | ] 720 | mypy-extensions = [ 721 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 722 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 723 | ] 724 | networkx = [ 725 | {file = "networkx-2.5-py3-none-any.whl", hash = "sha256:8c5812e9f798d37c50570d15c4a69d5710a18d77bafc903ee9c5fba7454c616c"}, 726 | {file = "networkx-2.5.tar.gz", hash = "sha256:7978955423fbc9639c10498878be59caf99b44dc304c2286162fd24b458c1602"}, 727 | ] 728 | packaging = [ 729 | {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, 730 | {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, 731 | ] 732 | pathspec = [ 733 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 734 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 735 | ] 736 | pluggy = [ 737 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 738 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 739 | ] 740 | py = [ 741 | {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, 742 | {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, 743 | ] 744 | pycodestyle = [ 745 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 746 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 747 | ] 748 | pydot = [ 749 | {file = "pydot-1.4.1-py2.py3-none-any.whl", hash = "sha256:67be714300c78fda5fd52f79ec994039e3f76f074948c67b5ff539b433ad354f"}, 750 | {file = "pydot-1.4.1.tar.gz", hash = "sha256:d49c9d4dd1913beec2a997f831543c8cbd53e535b1a739e921642fe416235f01"}, 751 | ] 752 | pyflakes = [ 753 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 754 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 755 | ] 756 | pygments = [ 757 | {file = "Pygments-2.7.2-py3-none-any.whl", hash = "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"}, 758 | {file = "Pygments-2.7.2.tar.gz", hash = "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"}, 759 | ] 760 | pyparsing = [ 761 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 762 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 763 | ] 764 | pytest = [ 765 | {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, 766 | {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, 767 | ] 768 | pytz = [ 769 | {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, 770 | {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, 771 | ] 772 | regex = [ 773 | {file = "regex-2020.10.28-cp27-cp27m-win32.whl", hash = "sha256:4b5a9bcb56cc146c3932c648603b24514447eafa6ce9295234767bf92f69b504"}, 774 | {file = "regex-2020.10.28-cp27-cp27m-win_amd64.whl", hash = "sha256:c13d311a4c4a8d671f5860317eb5f09591fbe8259676b86a85769423b544451e"}, 775 | {file = "regex-2020.10.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c454ad88e56e80e44f824ef8366bb7e4c3def12999151fd5c0ea76a18fe9aa3e"}, 776 | {file = "regex-2020.10.28-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c8a2b7ccff330ae4c460aff36626f911f918555660cc28163417cb84ffb25789"}, 777 | {file = "regex-2020.10.28-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4afa350f162551cf402bfa3cd8302165c8e03e689c897d185f16a167328cc6dd"}, 778 | {file = "regex-2020.10.28-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b88fa3b8a3469f22b4f13d045d9bd3eda797aa4e406fde0a2644bc92bbdd4bdd"}, 779 | {file = "regex-2020.10.28-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f43109822df2d3faac7aad79613f5f02e4eab0fc8ad7932d2e70e2a83bd49c26"}, 780 | {file = "regex-2020.10.28-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:de7fd57765398d141949946c84f3590a68cf5887dac3fc52388df0639b01eda4"}, 781 | {file = "regex-2020.10.28-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:9b6305295b6591e45f069d3553c54d50cc47629eb5c218aac99e0f7fafbf90a1"}, 782 | {file = "regex-2020.10.28-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:bd904c0dec29bbd0769887a816657491721d5f545c29e30fd9d7a1a275dc80ab"}, 783 | {file = "regex-2020.10.28-cp36-cp36m-win32.whl", hash = "sha256:8092a5a06ad9a7a247f2a76ace121183dc4e1a84c259cf9c2ce3bbb69fac3582"}, 784 | {file = "regex-2020.10.28-cp36-cp36m-win_amd64.whl", hash = "sha256:49461446b783945597c4076aea3f49aee4b4ce922bd241e4fcf62a3e7c61794c"}, 785 | {file = "regex-2020.10.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:297116e79074ec2a2f885d22db00ce6e88b15f75162c5e8b38f66ea734e73c64"}, 786 | {file = "regex-2020.10.28-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8ca9dca965bd86ea3631b975d63b0693566d3cc347e55786d5514988b6f5b84c"}, 787 | {file = "regex-2020.10.28-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ea37320877d56a7f0a1e6a625d892cf963aa7f570013499f5b8d5ab8402b5625"}, 788 | {file = "regex-2020.10.28-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:3a5f08039eee9ea195a89e180c5762bfb55258bfb9abb61a20d3abee3b37fd12"}, 789 | {file = "regex-2020.10.28-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:cb905f3d2e290a8b8f1579d3984f2cfa7c3a29cc7cba608540ceeed18513f520"}, 790 | {file = "regex-2020.10.28-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:96f99219dddb33e235a37283306834700b63170d7bb2a1ee17e41c6d589c8eb9"}, 791 | {file = "regex-2020.10.28-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:227a8d2e5282c2b8346e7f68aa759e0331a0b4a890b55a5cfbb28bd0261b84c0"}, 792 | {file = "regex-2020.10.28-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:2564def9ce0710d510b1fc7e5178ce2d20f75571f788b5197b3c8134c366f50c"}, 793 | {file = "regex-2020.10.28-cp37-cp37m-win32.whl", hash = "sha256:a62162be05edf64f819925ea88d09d18b09bebf20971b363ce0c24e8b4aa14c0"}, 794 | {file = "regex-2020.10.28-cp37-cp37m-win_amd64.whl", hash = "sha256:03855ee22980c3e4863dc84c42d6d2901133362db5daf4c36b710dd895d78f0a"}, 795 | {file = "regex-2020.10.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf4f896c42c63d1f22039ad57de2644c72587756c0cfb3cc3b7530cfe228277f"}, 796 | {file = "regex-2020.10.28-cp38-cp38-manylinux1_i686.whl", hash = "sha256:625116aca6c4b57c56ea3d70369cacc4d62fead4930f8329d242e4fe7a58ce4b"}, 797 | {file = "regex-2020.10.28-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dc522e25e57e88b4980d2bdd334825dbf6fa55f28a922fc3bfa60cc09e5ef53"}, 798 | {file = "regex-2020.10.28-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:119e0355dbdd4cf593b17f2fc5dbd4aec2b8899d0057e4957ba92f941f704bf5"}, 799 | {file = "regex-2020.10.28-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:cfcf28ed4ce9ced47b9b9670a4f0d3d3c0e4d4779ad4dadb1ad468b097f808aa"}, 800 | {file = "regex-2020.10.28-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b45bab9f224de276b7bc916f6306b86283f6aa8afe7ed4133423efb42015a898"}, 801 | {file = "regex-2020.10.28-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:52e83a5f28acd621ba8e71c2b816f6541af7144b69cc5859d17da76c436a5427"}, 802 | {file = "regex-2020.10.28-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:aacc8623ffe7999a97935eeabbd24b1ae701d08ea8f874a6ff050e93c3e658cf"}, 803 | {file = "regex-2020.10.28-cp38-cp38-win32.whl", hash = "sha256:06b52815d4ad38d6524666e0d50fe9173533c9cc145a5779b89733284e6f688f"}, 804 | {file = "regex-2020.10.28-cp38-cp38-win_amd64.whl", hash = "sha256:c3466a84fce42c2016113101018a9981804097bacbab029c2d5b4fcb224b89de"}, 805 | {file = "regex-2020.10.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:127a9e0c0d91af572fbb9e56d00a504dbd4c65e574ddda3d45b55722462210de"}, 806 | {file = "regex-2020.10.28-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c2c6c56ee97485a127555c9595c069201b5161de9d05495fbe2132b5ac104786"}, 807 | {file = "regex-2020.10.28-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1ec66700a10e3c75f1f92cbde36cca0d3aaee4c73dfa26699495a3a30b09093c"}, 808 | {file = "regex-2020.10.28-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:11116d424734fe356d8777f89d625f0df783251ada95d6261b4c36ad27a394bb"}, 809 | {file = "regex-2020.10.28-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f1fce1e4929157b2afeb4bb7069204d4370bab9f4fc03ca1fbec8bd601f8c87d"}, 810 | {file = "regex-2020.10.28-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3dfca201fa6b326239e1bccb00b915e058707028809b8ecc0cf6819ad233a740"}, 811 | {file = "regex-2020.10.28-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:b8a686a6c98872007aa41fdbb2e86dc03b287d951ff4a7f1da77fb7f14113e4d"}, 812 | {file = "regex-2020.10.28-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c32c91a0f1ac779cbd73e62430de3d3502bbc45ffe5bb6c376015acfa848144b"}, 813 | {file = "regex-2020.10.28-cp39-cp39-win32.whl", hash = "sha256:832339223b9ce56b7b15168e691ae654d345ac1635eeb367ade9ecfe0e66bee0"}, 814 | {file = "regex-2020.10.28-cp39-cp39-win_amd64.whl", hash = "sha256:654c1635f2313d0843028487db2191530bca45af61ca85d0b16555c399625b0e"}, 815 | {file = "regex-2020.10.28.tar.gz", hash = "sha256:dd3e6547ecf842a29cf25123fbf8d2461c53c8d37aa20d87ecee130c89b7079b"}, 816 | ] 817 | requests = [ 818 | {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, 819 | {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, 820 | ] 821 | six = [ 822 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 823 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 824 | ] 825 | snowballstemmer = [ 826 | {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, 827 | {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, 828 | ] 829 | sphinx = [ 830 | {file = "Sphinx-3.3.0-py3-none-any.whl", hash = "sha256:3abdb2c57a65afaaa4f8573cbabd5465078eb6fd282c1e4f87f006875a7ec0c7"}, 831 | {file = "Sphinx-3.3.0.tar.gz", hash = "sha256:1c21e7c5481a31b531e6cbf59c3292852ccde175b504b00ce2ff0b8f4adc3649"}, 832 | ] 833 | sphinxcontrib-applehelp = [ 834 | {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, 835 | {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, 836 | ] 837 | sphinxcontrib-devhelp = [ 838 | {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, 839 | {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, 840 | ] 841 | sphinxcontrib-htmlhelp = [ 842 | {file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"}, 843 | {file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"}, 844 | ] 845 | sphinxcontrib-jsmath = [ 846 | {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, 847 | {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, 848 | ] 849 | sphinxcontrib-qthelp = [ 850 | {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, 851 | {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, 852 | ] 853 | sphinxcontrib-serializinghtml = [ 854 | {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, 855 | {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, 856 | ] 857 | toml = [ 858 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 859 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 860 | ] 861 | typed-ast = [ 862 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 863 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 864 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 865 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 866 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 867 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 868 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 869 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, 870 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 871 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 872 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 873 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 874 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 875 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, 876 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 877 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 878 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 879 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 880 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 881 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, 882 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 883 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 884 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 885 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, 886 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, 887 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, 888 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, 889 | {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, 890 | {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, 891 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 892 | ] 893 | typing-extensions = [ 894 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 895 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 896 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 897 | ] 898 | urllib3 = [ 899 | {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, 900 | {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, 901 | ] 902 | zipp = [ 903 | {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, 904 | {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, 905 | ] 906 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "depx" 3 | version = "0.2.0" 4 | description = "Examine and visualize dependencies used by Python modules." 5 | authors = [ 6 | "Ana Paula Gomes ", 7 | "Til Boerner ", 8 | "Thermondo " 9 | ] 10 | license = "GPL-3.0-or-later" 11 | readme = "README.md" 12 | homepage = "https://github.com/tilboerner/depx" 13 | repository = "https://github.com/tilboerner/depx.git" 14 | classifiers = [ 15 | "Development Status :: 2 - Pre-Alpha", 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 18 | ] 19 | 20 | [tool.poetry.scripts] 21 | depx = "depx.cli:main" 22 | 23 | [tool.poetry.urls] 24 | "Bug Tracker" = "https://github.com/tilboerner/depx/issues" 25 | 26 | [tool.poetry.dependencies] 27 | python = "^3.6" 28 | Click = "^7.1.2" 29 | jinja2 = "^2.11" 30 | networkx = "^2.3" 31 | pydot = "^1.4" 32 | 33 | [tool.poetry.dev-dependencies] 34 | pytest = "^6.1" 35 | flake8 = "^3.8.4" 36 | coverage = "^5.3" 37 | Sphinx = "^3.3" 38 | black = "^20.8b1" 39 | 40 | [build-system] 41 | requires = ["poetry>=1.0"] 42 | build-backend = "poetry.masonry.api" 43 | 44 | [tool.black] 45 | line-length = 88 46 | skip-string-normalization = true 47 | target-version = ['py36'] 48 | include = '\.pyi?$' 49 | exclude = ''' 50 | 51 | ( 52 | /( 53 | \.eggs # exclude a few common directories in the 54 | | \.git # root of the project 55 | | \.hg 56 | | \.mypy_cache 57 | | \.tox 58 | | \.venv 59 | | _build 60 | | buck-out 61 | | build 62 | | dist 63 | )/ 64 | ) 65 | ''' 66 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = docs 3 | max-line-length = 99 4 | 5 | [pydocstyle] 6 | add-ignore = D1 7 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilboerner/depx/0f44d747844b0f370bf58544741d86251da5d8d6/tests/__init__.py -------------------------------------------------------------------------------- /tests/fake_project/another_fake_module/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilboerner/depx/0f44d747844b0f370bf58544741d86251da5d8d6/tests/fake_project/another_fake_module/__init__.py -------------------------------------------------------------------------------- /tests/fake_project/another_fake_module/missing_creativity.py: -------------------------------------------------------------------------------- 1 | from fake_module.another_module import oh_nice 2 | import random 3 | 4 | 5 | def pick_a_number(): 6 | print(oh_nice()) 7 | return random.randint(1, 10) 8 | -------------------------------------------------------------------------------- /tests/fake_project/fake_module/__init__.py: -------------------------------------------------------------------------------- 1 | import fake_module.a_module 2 | from fake_module import another_module 3 | 4 | 5 | fake_module.a_module.oh_nice() 6 | another_module.oh_nice() 7 | -------------------------------------------------------------------------------- /tests/fake_project/fake_module/a_module.py: -------------------------------------------------------------------------------- 1 | from .another_module import oh_nice 2 | 3 | 4 | oh_nice() 5 | -------------------------------------------------------------------------------- /tests/fake_project/fake_module/another_module.py: -------------------------------------------------------------------------------- 1 | def oh_nice(): 2 | return 'oh, nice!' 3 | -------------------------------------------------------------------------------- /tests/fake_project/random_folder/cool_file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilboerner/depx/0f44d747844b0f370bf58544741d86251da5d8d6/tests/fake_project/random_folder/cool_file.txt -------------------------------------------------------------------------------- /tests/fake_project/random_folder/python_file_without_py_extension.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | -------------------------------------------------------------------------------- /tests/fake_project/setup.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tilboerner/depx/0f44d747844b0f370bf58544741d86251da5d8d6/tests/fake_project/setup.py -------------------------------------------------------------------------------- /tests/test_depx.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | import pytest 3 | 4 | import depx 5 | from depx import cli 6 | 7 | 8 | fake_module = 'tests/fake_project/fake_module' 9 | 10 | 11 | def test_package_version_matches_project(): 12 | import pkg_resources 13 | 14 | assert depx.__version__ == pkg_resources.get_distribution('depx').version 15 | 16 | 17 | def test_command_line_interface(): 18 | runner = CliRunner() 19 | run_result = runner.invoke(cli.main, [fake_module]) 20 | 21 | assert run_result.exit_code == 0 22 | 23 | 24 | @pytest.mark.parametrize('format', ['json', 'html', 'graphml', 'dot']) 25 | def test_export_to(format): 26 | runner = CliRunner() 27 | run_result = runner.invoke(cli.main, [fake_module, '--format', format]) 28 | 29 | assert run_result.exit_code == 0 30 | -------------------------------------------------------------------------------- /tests/test_graph.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from depx.graph import create_graph_from, to_html, to_graphml, to_dotfile, to_json 4 | import networkx as nx 5 | import io 6 | import os 7 | import pytest 8 | 9 | 10 | @pytest.fixture 11 | def dependencies(): 12 | return [ 13 | { 14 | "from_module": "opportunity.models", 15 | "to_module": "common.utils", 16 | "category": "local", 17 | }, 18 | { 19 | "from_module": "opportunity.models", 20 | "to_module": "common.utils", 21 | "category": "local", 22 | }, 23 | { 24 | "from_module": "sales_appoinment.views", 25 | "to_module": "common.utils", 26 | "category": "local", 27 | }, 28 | { 29 | "from_module": "common.utils", 30 | "to_module": "opportunity.views", 31 | "category": "local", 32 | }, 33 | ] 34 | 35 | 36 | def test_create_graph(dependencies): 37 | graph = create_graph_from(dependencies) 38 | 39 | expected_nodes = [ 40 | 'opportunity.models', 41 | 'common.utils', 42 | 'sales_appoinment.views', 43 | 'opportunity.views', 44 | ] 45 | expected_edges = [ 46 | ('opportunity.models', 'common.utils', {'weight': 2}), 47 | ('common.utils', 'opportunity.views', {'weight': 1}), 48 | ('sales_appoinment.views', 'common.utils', {'weight': 1}), 49 | ] 50 | for node in graph.nodes(): 51 | assert node in expected_nodes 52 | expected_nodes.remove(node) 53 | 54 | assert expected_nodes == [] 55 | 56 | for edge in graph.edges(data=True): 57 | assert edge in expected_edges 58 | expected_edges.remove(edge) 59 | 60 | assert expected_edges == [] 61 | 62 | 63 | def test_format_to_html(dependencies): 64 | graph = create_graph_from(dependencies) 65 | 66 | content = to_html(graph=graph, path=os.getcwd()) 67 | 68 | assert '' in content 69 | 70 | 71 | def test_format_to_graphml(dependencies): 72 | graph = create_graph_from(dependencies) 73 | content = to_graphml(graph=graph, path=os.getcwd()) 74 | 75 | exported_graph = nx.read_graphml(io.StringIO(content)) 76 | 77 | assert exported_graph.nodes() == graph.nodes() 78 | assert exported_graph.edges() == graph.edges() 79 | 80 | 81 | def test_format_to_dotfile(dependencies): 82 | graph = create_graph_from(dependencies) 83 | content = to_dotfile(graph=graph, path=os.getcwd()) 84 | 85 | exported_graph = nx.drawing.nx_pydot.read_dot(io.StringIO(content)) 86 | 87 | assert exported_graph.nodes() == graph.nodes() 88 | assert ( 89 | nx.to_dict_of_dicts(graph).keys() == nx.to_dict_of_dicts(exported_graph).keys() 90 | ) 91 | 92 | 93 | def test_format_to_json(dependencies): 94 | content = to_json(dependencies=dependencies) 95 | 96 | assert json.loads(content) == dependencies 97 | -------------------------------------------------------------------------------- /tests/test_parsing.py: -------------------------------------------------------------------------------- 1 | from operator import itemgetter 2 | 3 | from depx.parsing import ( 4 | find_imports_from_text, 5 | _dependency, 6 | find_imports, 7 | _is_package, 8 | _is_module, 9 | _find_base_name, 10 | filter_top_level_names, 11 | ) 12 | import os 13 | import pytest 14 | import sys 15 | from textwrap import dedent 16 | 17 | 18 | @pytest.mark.parametrize( 19 | 'base_name,text,expected', 20 | [ 21 | ( 22 | 'SINGLE_IMPORT', 23 | 'import some_module', 24 | [ 25 | _dependency( 26 | from_module='SINGLE_IMPORT', 27 | from_name='some_module', 28 | to_module='some_module', 29 | to_name='some_module', 30 | category='module', 31 | ), 32 | ], 33 | ), 34 | ( 35 | 'ALIAS_IMPORT', 36 | 'import some_module as alias_name', 37 | [ 38 | _dependency( 39 | from_module='ALIAS_IMPORT', 40 | from_name='alias_name', 41 | to_module='some_module', 42 | to_name='some_module', 43 | category='module', 44 | ), 45 | ], 46 | ), 47 | ( 48 | 'MULTI_IMPORT_ONE_LINE', 49 | 'import some_module, other_module as other_alias', 50 | [ 51 | _dependency( 52 | from_module='MULTI_IMPORT_ONE_LINE', 53 | from_name='some_module', 54 | to_module='some_module', 55 | to_name='some_module', 56 | category='module', 57 | ), 58 | _dependency( 59 | from_module='MULTI_IMPORT_ONE_LINE', 60 | from_name='other_alias', 61 | to_module='other_module', 62 | to_name='other_module', 63 | category='module', 64 | ), 65 | ], 66 | ), 67 | ( 68 | 'FROM_NAMESPACE_IMPORT_NAME', 69 | 'from some_namespace import some_name', 70 | [ 71 | _dependency( 72 | from_module='FROM_NAMESPACE_IMPORT_NAME', 73 | from_name='some_name', 74 | to_module='some_namespace', 75 | to_name='some_name', 76 | category='module', 77 | is_relative=False, 78 | level=0, 79 | ), 80 | ], 81 | ), 82 | ( 83 | 'FROM_NAMESPACE_IMPORT_NAME_ALIAS', 84 | 'from some_namespace import some_name as alias_name', 85 | [ 86 | _dependency( 87 | from_module='FROM_NAMESPACE_IMPORT_NAME_ALIAS', 88 | from_name='alias_name', 89 | to_module='some_namespace', 90 | to_name='some_name', 91 | category='module', 92 | is_relative=False, 93 | level=0, 94 | ), 95 | ], 96 | ), 97 | ( 98 | 'BASE_PACKAGE.RELATIVE_IMPORT', 99 | 'from . import some_module', 100 | [ 101 | _dependency( 102 | from_module='BASE_PACKAGE.RELATIVE_IMPORT', 103 | from_name='some_module', 104 | to_module='BASE_PACKAGE.some_module', 105 | to_name='some_module', 106 | category='module', 107 | is_relative=True, 108 | level=1, 109 | ), 110 | ], 111 | ), 112 | ( 113 | 'BASE_PACKAGE.RELATIVE_IMPORT_TO_SIBLING', 114 | 'from .sibling_namespace import some_name', 115 | [ 116 | _dependency( 117 | from_module='BASE_PACKAGE.RELATIVE_IMPORT_TO_SIBLING', 118 | from_name='some_name', 119 | to_module='BASE_PACKAGE.sibling_namespace', 120 | to_name='some_name', 121 | category='module', 122 | is_relative=True, 123 | level=1, 124 | ), 125 | ], 126 | ), 127 | ( 128 | 'RELATIVE_IMPORT_FROM_ROOT', 129 | 'from . import some_module', 130 | [ 131 | _dependency( 132 | from_module='RELATIVE_IMPORT_FROM_ROOT', 133 | from_name='some_module', 134 | to_module='some_module', 135 | to_name='some_module', 136 | category='module', 137 | is_relative=True, 138 | level=1, 139 | ), 140 | ], 141 | ), 142 | ( 143 | 'LOCAL_IMPORT', 144 | """ 145 | def function(): 146 | import some_module 147 | """, 148 | [ 149 | _dependency( 150 | from_module='LOCAL_IMPORT', 151 | from_name='some_module', 152 | to_module='some_module', 153 | to_name='some_module', 154 | category='local', 155 | ), 156 | ], 157 | ), 158 | ], 159 | ) 160 | def test_find_imports_from_text(base_name, text, expected): 161 | text = dedent(text) 162 | assert list(find_imports_from_text(text, base_name)) == expected 163 | 164 | 165 | @pytest.mark.skipif(sys.version_info < (3, 5), reason='async not in older Pythons') 166 | def test_find_imports_from_text__async_def(): 167 | imports = list( 168 | find_imports_from_text( 169 | dedent( 170 | """ 171 | async def function(): 172 | import some_module 173 | """ 174 | ), 175 | base_name='TEST_MODULE', 176 | ) 177 | ) 178 | 179 | assert imports == [ 180 | _dependency( 181 | from_module='TEST_MODULE', 182 | from_name='some_module', 183 | to_module='some_module', 184 | to_name='some_module', 185 | category='local', 186 | ), 187 | ] 188 | 189 | 190 | @pytest.mark.parametrize( 191 | 'path,expected', 192 | [ 193 | (os.getcwd() + '/tests/fake_project/another_fake_module', True), 194 | (os.getcwd() + '/tests/fake_project/fake_module', True), 195 | (os.getcwd() + '/tests/fake_project/random_folder', False), 196 | ], 197 | ) 198 | def test_is_package(path, expected): 199 | assert _is_package(path) is expected 200 | 201 | 202 | def test_find_imports(): 203 | expected = [ 204 | { 205 | 'from_module': 'another_fake_module.missing_creativity', 206 | 'to_module': 'fake_module.another_module', 207 | 'category': 'module', 208 | 'is_relative': False, 209 | 'from_name': 'oh_nice', 210 | 'to_name': 'oh_nice', 211 | 'level': 0, 212 | }, 213 | { 214 | 'from_module': 'another_fake_module.missing_creativity', 215 | 'to_module': 'random', # FIXME identify as internal 216 | 'category': 'module', 217 | 'is_relative': False, 218 | 'from_name': 'random', 219 | 'to_name': 'random', 220 | 'level': 0, 221 | }, 222 | { 223 | 'from_module': 'fake_module.__init__', 224 | 'to_module': 'fake_module.a_module', 225 | 'category': 'module', 226 | 'is_relative': False, 227 | 'from_name': 'fake_module.a_module', 228 | 'to_name': 'fake_module.a_module', 229 | 'level': 0, 230 | }, 231 | { 232 | 'from_module': 'fake_module.__init__', 233 | 'to_module': 'fake_module', 234 | 'category': 'module', 235 | 'is_relative': False, 236 | 'from_name': 'another_module', 237 | 'to_name': 'another_module', 238 | 'level': 0, 239 | }, 240 | { 241 | 'from_module': 'fake_module.a_module', 242 | 'to_module': 'fake_module.another_module', 243 | 'category': 'module', 244 | 'is_relative': True, 245 | 'from_name': 'oh_nice', 246 | 'to_name': 'oh_nice', 247 | 'level': 1, 248 | }, 249 | ] 250 | by_name = itemgetter('from_module', 'from_name', 'to_module', 'to_name') 251 | assert sorted(find_imports('tests/fake_project'), key=by_name) == sorted( 252 | expected, key=by_name 253 | ) 254 | 255 | 256 | @pytest.mark.parametrize( 257 | 'path,expected', 258 | [ 259 | (os.getcwd() + '/tests/fake_project/another_fake_module/__init__.py', True), 260 | ( 261 | os.getcwd() 262 | + '/tests/fake_project/another_fake_module/missing_creativity.py', 263 | True, 264 | ), 265 | (os.getcwd() + '/tests/fake_project/setup.py', True), 266 | ( 267 | os.getcwd() 268 | + '/tests/fake_project/random_folder/python_file_without_py_extension.txt', 269 | True, 270 | ), 271 | (os.getcwd() + '/tests/fake_project/random_folder/cool_file.txt', False), 272 | ], 273 | ) 274 | def test_is_module(path, expected): 275 | assert _is_module(path) is expected 276 | 277 | 278 | @pytest.mark.parametrize( 279 | 'path,expected', 280 | [ 281 | ('tests/fake_project/another_fake_module/__init__.py', 'another_fake_module'), 282 | ( 283 | os.getcwd() 284 | + '/tests/fake_project/another_fake_module/missing_creativity.py', 285 | 'another_fake_module', 286 | ), 287 | ('tests/fake_project/setup.py', ''), 288 | ], 289 | ) 290 | def test_find_base_name(path, expected): 291 | assert _find_base_name(path) == expected 292 | 293 | 294 | def test_filter_top_level_names(): 295 | dependency = { 296 | 'from_module': 'a.b', 297 | 'to_module': 'x.y.z', 298 | 'from_name': 'SOMETHING', 299 | 'to_name': 'SOMETHING ELSE', 300 | } 301 | expected = { 302 | 'from_module': 'a', 303 | 'to_module': 'x', 304 | 'from_name': '', 305 | 'to_name': '', 306 | } 307 | assert list(filter_top_level_names([dependency])) == [expected] 308 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, py39, flake8 3 | isolated_build = true 4 | 5 | [travis] 6 | python = 7 | 3.9: py39 8 | 3.8: py38 9 | 3.7: py37 10 | 3.6: py36 11 | 12 | [testenv:flake8] 13 | basepython = python 14 | deps = flake8 15 | commands = flake8 depx 16 | 17 | [testenv] 18 | deps = pytest 19 | commands = pytest --basetemp={envtmpdir} 20 | --------------------------------------------------------------------------------