diff --git a/README.md b/README.md index b291c0f4..e0087b45 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ --- -__deptry__ is a command line tool to check for unused dependencies in a poetry managed python project. It does so by scanning the imported modules within all `.py` files in +__deptry__ is a command line tool to check for unused dependencies in a poetry managed Python project. It does so by scanning the imported modules within all Python files in a directory and it's subdirectories, and comparing those to the dependencies listed in `pyproject.toml`. --- diff --git a/deptry/__init__.py b/deptry/__init__.py index e69de29b..53ac505c 100644 --- a/deptry/__init__.py +++ b/deptry/__init__.py @@ -0,0 +1,3 @@ +import logging + +logging.getLogger("nbconvert").setLevel(logging.WARNING) diff --git a/deptry/cli.py b/deptry/cli.py index c8dc64ca..5fa38820 100644 --- a/deptry/cli.py +++ b/deptry/cli.py @@ -32,7 +32,13 @@ def deptry(): help="""Directories in which .py files should not be scanned for imports to determine if a dependency is used or not. Defaults to 'venv'. Specify multiple directories by using this flag twice, e.g. `-id .venv -id other_dir`""", ) -def check(verbose, ignore_dependencies, ignore_directories): +@click.option( + "--ignore-notebooks", + "-nb", + is_flag=True, + help="Boolean flag to specify if notebooks should be ignored while scanning for imports.", +) +def check(verbose, ignore_dependencies, ignore_directories, ignore_notebooks): log_level = logging.DEBUG if verbose else logging.INFO logging.basicConfig(level=log_level, handlers=[logging.StreamHandler()], format="%(message)s") @@ -42,15 +48,20 @@ def check(verbose, ignore_dependencies, ignore_directories): cli_arguments["ignore_dependencies"] = list(ignore_dependencies) if len(ignore_directories) > 0: cli_arguments["ignore_directories"] = list(ignore_directories) + if ignore_notebooks: + cli_arguments["ignore_notebooks"] = True config = Config(cli_arguments) obsolete_dependencies = Core( - ignore_dependencies=config.config["ignore_dependencies"], ignore_directories=config.config["ignore_directories"] + ignore_dependencies=config.config["ignore_dependencies"], + ignore_directories=config.config["ignore_directories"], + ignore_notebooks=config.config["ignore_notebooks"], ).run() if len(obsolete_dependencies): logging.info(f"pyproject.toml contains obsolete dependencies: {obsolete_dependencies}") sys.exit(1) else: + logging.info("Succes! No obsolete dependencies found.") sys.exit(0) diff --git a/deptry/config.py b/deptry/config.py index 749ffb40..a66e14f7 100644 --- a/deptry/config.py +++ b/deptry/config.py @@ -4,7 +4,7 @@ import toml -DEFAULTS = {"ignore_dependencies": None, "ignore_directories": [".venv"]} +DEFAULTS = {"ignore_dependencies": None, "ignore_directories": [".venv"], "ignore_notebooks": False} class Config: diff --git a/deptry/core.py b/deptry/core.py index b63ef82d..c2d0dde5 100644 --- a/deptry/core.py +++ b/deptry/core.py @@ -1,3 +1,4 @@ +from pathlib import Path from typing import List from deptry.import_parser import ImportParser @@ -7,13 +8,16 @@ class Core: - def __init__(self, ignore_dependencies: List[str] = None, ignore_directories: List[str] = None): + def __init__(self, ignore_dependencies: List[str], ignore_directories: List[str], ignore_notebooks: bool): self.ignore_dependencies = ignore_dependencies self.ignore_directories = ignore_directories + self.ignore_notebooks = ignore_notebooks def run(self): - all_py_files = PythonFileFinder(ignore_directories=self.ignore_directories).get_list_of_python_files() - imported_modules = ImportParser().get_imported_modules_for_list_of_files(all_py_files) + all_python_files = PythonFileFinder( + ignore_directories=self.ignore_directories, ignore_notebooks=self.ignore_notebooks + ).get_all_python_files_in(Path(".")) + imported_modules = ImportParser().get_imported_modules_for_list_of_files(all_python_files) imported_packages = ImportsToPackageNames().convert(imported_modules) obsolete_dependencies = ObsoleteDependenciesFinder( imported_packages=imported_packages, ignore_dependencies=self.ignore_dependencies diff --git a/deptry/import_parser.py b/deptry/import_parser.py index 9b257144..ed96aece 100644 --- a/deptry/import_parser.py +++ b/deptry/import_parser.py @@ -3,40 +3,60 @@ from pathlib import Path from typing import List +from deptry.notebook_import_extractor import NotebookImportExtractor + class ImportParser: """ - Get a list of imported modules from a python file. + Scan a Python file for import statements and return a list of imported modules. """ def __init__(self) -> None: pass - def get_imported_modules_for_file(self, path_to_py_file: Path) -> List[str]: + def get_imported_modules_for_list_of_files(self, list_of_files: List[Path]) -> List[str]: + modules_per_file = [self._get_imported_modules_from_file(file) for file in list_of_files] + all_modules = self._flatten_list(modules_per_file) + unique_modules = sorted(list(set(all_modules))) + logging.debug(f"All imported modules: {unique_modules}\n") + return unique_modules + + def _get_imported_modules_from_file(self, path_to_file: Path) -> List[str]: try: - modules = [] - with open(path_to_py_file) as f: - root = ast.parse(f.read(), path_to_py_file) - - for node in ast.iter_child_nodes(root): - if isinstance(node, ast.Import): - modules += [x.name.split(".")[0] for x in node.names] - elif isinstance(node, ast.ImportFrom): - modules.append(node.module.split(".")[0]) - logging.debug(f"Found the following imports in {str(path_to_py_file)}: {modules}") - return modules - except: # noqa - logging.warning(f"Warning: Parsing imports for file {str(path_to_py_file)} failed.") - - def get_imported_modules_for_list_of_files(self, list_of_paths: List[Path]) -> List[str]: - modules_per_file = [ - {"path": str(path), "modules": self.get_imported_modules_for_file(path)} for path in list_of_paths - ] + if str(path_to_file).endswith(".ipynb"): + modules = self._get_imported_modules_from_ipynb(path_to_file) + else: + modules = self._get_imported_modules_from_py(path_to_file) + logging.debug(f"Found the following imports in {str(path_to_file)}: {modules}") + except Exception as e: + logging.warning(f"Warning: Parsing imports for file {str(path_to_file)} failed.") + raise (e) + return modules + + def _get_imported_modules_from_py(self, path_to_py_file: Path): + with open(path_to_py_file) as f: + root = ast.parse(f.read(), path_to_py_file) + return self._get_modules_from_ast_root(root) + + def _get_imported_modules_from_ipynb(self, path_to_ipynb_file: Path): + imports = NotebookImportExtractor().extract(path_to_ipynb_file) + root = ast.parse("\n".join(imports)) + return self._get_modules_from_ast_root(root) + + @staticmethod + def _get_modules_from_ast_root(root): modules = [] - for file in modules_per_file: - if file["modules"]: - modules += file["modules"] + for node in ast.iter_child_nodes(root): + if isinstance(node, ast.Import): + modules += [x.name.split(".")[0] for x in node.names] + elif isinstance(node, ast.ImportFrom): + modules.append(node.module.split(".")[0]) + return modules - unique_modules = sorted(list(set(modules))) - logging.debug(f"All imported modules: {unique_modules}\n") - return unique_modules + @staticmethod + def _flatten_list(modules_per_file): + all_modules = [] + for modules in modules_per_file: + if modules: + all_modules += modules + return all_modules diff --git a/deptry/imports_to_package_names.py b/deptry/imports_to_package_names.py index 1d8964a0..780f9046 100644 --- a/deptry/imports_to_package_names.py +++ b/deptry/imports_to_package_names.py @@ -16,7 +16,7 @@ class ImportsToPackageNames: For a list of imported modules, find for each module (e.g. python_dateutil) the corresponding package name used to install it (e.g. python-dateutil) and return those names as a list. - There are two reasons that can cause the corresponding package name not to be found: + There are three reasons that can cause the corresponding package name not to be found: - The package is in the Python standard library. In this case, nothing is added to the output list. - The package lacks metadata. In this case, a warning is raised. - The package is not installed. diff --git a/deptry/notebook_import_extractor.py b/deptry/notebook_import_extractor.py new file mode 100644 index 00000000..3f1ee6dd --- /dev/null +++ b/deptry/notebook_import_extractor.py @@ -0,0 +1,47 @@ +import json +import re +from pathlib import Path +from typing import List + + +class NotebookImportExtractor: + """ + Class to extract import statements from a Jupyter notebook. + """ + + def __init__(self) -> None: + pass + + def extract(self, path_to_ipynb: Path) -> List[str]: + """ + Extract import statements from a Jupyter notebook and return them as a list of strings, where + each element in the list is one of the import statements. + + Args: + path_to_ipynb: Path to the .ipynb file to extract inputs from + """ + notebook = self._read_ipynb_file(path_to_ipynb) + cells = self._keep_code_cells(notebook) + import_statements = [self._extract_import_statements_from_cell(cell) for cell in cells] + return self._flatten(import_statements) + + @staticmethod + def _read_ipynb_file(path_to_ipynb: Path) -> dict: + with open(path_to_ipynb, "r") as f: + notebook = json.load(f) + return notebook + + @staticmethod + def _keep_code_cells(notebook: dict) -> List[dict]: + return [cell for cell in notebook["cells"] if cell["cell_type"] == "code"] + + @staticmethod + def _contains_import_statements(line: str) -> bool: + return re.search(r"^(?:from\s+(\w+)(?:\.\w+)?\s+)?import\s+([^\s,.]+)(?:\.\w+)?", line) is not None + + def _extract_import_statements_from_cell(self, cell: dict) -> str: + return [line for line in cell["source"] if self._contains_import_statements(line)] + + @staticmethod + def _flatten(list_of_lists: List[List]) -> List: + return [item for sublist in list_of_lists for item in sublist] diff --git a/deptry/python_file_finder.py b/deptry/python_file_finder.py index 0ba9800d..8bc7f836 100644 --- a/deptry/python_file_finder.py +++ b/deptry/python_file_finder.py @@ -6,28 +6,29 @@ class PythonFileFinder: """ Get a list of all .py and .ipynb files recursively within a directory. + If ignore_notebooks is set to True, .ipynb files are ignored and only .py files are returned. """ - def __init__(self, ignore_directories: List[str] = [".venv"], include_ipynb: bool = False) -> None: + def __init__(self, ignore_directories: List[str] = [".venv"], ignore_notebooks: bool = False) -> None: self.ignore_directories = ignore_directories - self.include_ipynb = include_ipynb + self.ignore_notebooks = ignore_notebooks - def get_list_of_python_files(self): - all_py_files = self._get_all_python_files() - if self.include_ipynb: - all_py_files = self._add_ipynb_files(all_py_files) - all_py_files = self._remove_ignore_directories(all_py_files) + def get_all_python_files_in(self, directory: Path): + all_python_files = self._get_all_py_files_in(directory) + if not self.ignore_notebooks: + all_python_files += self._get_all_ipynb_files_in(directory) + all_python_files = self._remove_directories_to_ignore(all_python_files) nl = "\n" - logging.debug(f"Python files to scan for imports:\n{nl.join([str(x) for x in all_py_files])}\n") - return all_py_files + logging.debug(f"Python files to scan for imports:\n{nl.join([str(x) for x in all_python_files])}\n") + return all_python_files - def _get_all_python_files(self) -> List[Path]: - return [path for path in Path(".").rglob("*.py")] + def _get_all_py_files_in(self, directory: Path) -> List[Path]: + return [path for path in directory.rglob("*.py")] - def _add_ipynb_files(self, all_py_files: List[Path]) -> List[Path]: - return all_py_files + [path for path in Path(".").rglob("*.ipynb")] + def _get_all_ipynb_files_in(self, directory: Path) -> List[Path]: + return [path for path in directory.rglob("*.ipynb")] - def _remove_ignore_directories(self, all_py_files: List[Path]) -> List[Path]: + def _remove_directories_to_ignore(self, all_py_files: List[Path]) -> List[Path]: return [ path for path in all_py_files diff --git a/docs/index.md b/docs/index.md index d47f3104..02c656b4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ --- -_deptry_ is a command line tool to check for unused dependencies in a poetry managed python project. It does so by scanning the imported modules within all `.py` files in +_deptry_ is a command line tool to check for unused dependencies in a poetry managed Python project. It does so by scanning the imported modules within all Python files in a directory and it's subdirectories, and comparing those to the dependencies listed in `pyproject.toml`. ## Installation and usage diff --git a/docs/usage.md b/docs/usage.md index c778acb2..72f17a66 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -43,7 +43,7 @@ deptry check -i pandas -i numpy ## Ignore directories -_deptry_ scans the working directory and it's subdirectories recursively for `.py` files to scan for import statements. By default, +_deptry_ scans the working directory and it's subdirectories recursively for `.py` and `.ipynb` files to scan for import statements. By default, the `.venv` directory is ignored. To ignore other directories, use the `-id` flag. Note that this overwrites the default, so to ignore both the `.venv` directory and another directory, use the flag twice: @@ -51,6 +51,14 @@ both the `.venv` directory and another directory, use the flag twice: deptry check -id .venv -id other_directory ``` +## Ignore notebooks + +By default, _deptry_ scans the working directory for `.py` and `.ipynb` files to check for import statements. To ignore `.ipynb` files, use the `--ignore-notebooks` flag: + +```sh +deptry check --ignore-notebooks +``` + ## pyproject.toml _deptry_ can also be configured through `pyproject.toml`. An example `pyproject.toml` entry for `deptry` looks as follows: @@ -64,6 +72,7 @@ ignore_directories = [ '.venv', 'other_directory' ] +ignore_notebooks = false ``` ## Lookup hierarchy diff --git a/notebooks/Untitled-Copy1.ipynb b/notebooks/Untitled-Copy1.ipynb new file mode 100644 index 00000000..05b0541f --- /dev/null +++ b/notebooks/Untitled-Copy1.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "ff081f8f-be58-4866-8450-ff64c92ff19f", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "import toml\n", + "from pathlib import Path\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fa335be4-bfa4-42c5-8d64-f9dbe16c8840", + "metadata": {}, + "outputs": [], + "source": [ + "from deptry.import_parser import ImportParser" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "da2fba71-27fe-4136-981a-be5f4ef07f38", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['click', 'numpy', 'os', 'pandas', 'pathlib']" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "files = [Path('../tests/data/projects/project_with_obsolete/src/main.py')]\n", + "ImportParser('tmp').get_imported_modules_for_list_of_files(files)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44edb9bc-b038-4a95-ad3c-79180a5631e6", + "metadata": {}, + "outputs": [], + "source": [ + "from deptry.core import Core\n", + "Core().run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "627148ff-db93-40c3-a95d-3024ba861a63", + "metadata": {}, + "outputs": [], + "source": [ + "pyproject_text = Path(\"./pyproject.toml\").read_text()\n", + "pyproject_data = toml.loads(pyproject_text)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "228ea9e9-966c-4a2d-8283-6a2823064bc6", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/Untitled1.ipynb b/notebooks/Untitled1.ipynb deleted file mode 100644 index 11efc5c4..00000000 --- a/notebooks/Untitled1.ipynb +++ /dev/null @@ -1,116 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "9f4924ec-2200-4801-9d49-d4833651cbc4", - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "import toml\n", - "from pathlib import Path\n", - "import os\n", - "os.chdir('../../da-ffilo-common')" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "fa1efd0c-2f0c-4b0b-ab4c-3fe4403d1432", - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "sys.path.append('/Users/florian.maas/Library/Caches/pypoetry/virtualenvs/da-ffilo-common-dHisq-Qd-py3.9/lib/python3.9/site-packages')" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "7160a2ba-0367-46b9-a0ed-b532f9b43b3a", - "metadata": {}, - "outputs": [], - "source": [ - "import ast\n", - "import logging\n", - "from pathlib import Path\n", - "from typing import List" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "4e67d61e-7b0e-48f4-863b-ac0cbc8c2b0d", - "metadata": {}, - "outputs": [], - "source": [ - "path = Path('da_ffilo_common/gcp/bq/writer.py')" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "cbabf3c3-f003-4cbc-bb8e-9d38775f63f9", - "metadata": {}, - "outputs": [], - "source": [ - "with open(path) as f:\n", - " root = ast.parse(f.read(), path)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "69d972b3-0252-426d-850d-98419ed75df5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'module': 'typing', 'names': [, , , ], 'level': 0, 'lineno': 1, 'col_offset': 0, 'end_lineno': 1, 'end_col_offset': 44}\n", - "{'module': 'google.cloud', 'names': [], 'level': 0, 'lineno': 3, 'col_offset': 0, 'end_lineno': 3, 'end_col_offset': 33}\n", - "{'module': 'da_ffilo_common.gcp.bq.client', 'names': [], 'level': 0, 'lineno': 5, 'col_offset': 0, 'end_lineno': 5, 'end_col_offset': 56}\n", - "{'module': 'da_ffilo_common.gcp.errors', 'names': [], 'level': 0, 'lineno': 6, 'col_offset': 0, 'end_lineno': 6, 'end_col_offset': 58}\n", - "{'name': 'BigQueryWriter', 'bases': [], 'keywords': [], 'body': [, , , ], 'decorator_list': [], 'lineno': 9, 'col_offset': 0, 'end_lineno': 90, 'end_col_offset': 93}\n" - ] - } - ], - "source": [ - "for node in ast.iter_child_nodes(root):\n", - " print(node.__dict__)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c613796a-f6c1-44ad-91c8-41d96e83db59", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/poetry.lock b/poetry.lock index dd1047b6..8a9bb912 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,7 +2,7 @@ name = "anyio" version = "3.6.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" +category = "dev" optional = false python-versions = ">=3.6.2" @@ -27,7 +27,7 @@ python-versions = "*" name = "argon2-cffi" version = "21.3.0" description = "The secure Argon2 password hashing algorithm." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -43,7 +43,7 @@ tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] name = "argon2-cffi-bindings" version = "21.2.0" description = "Low-level CFFI bindings for Argon2" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -54,17 +54,6 @@ cffi = ">=1.0.1" dev = ["pytest", "cogapp", "pre-commit", "wheel"] tests = ["pytest"] -[[package]] -name = "arrow" -version = "1.2.2" -description = "Better dates & times for Python" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -python-dateutil = ">=2.7.0" - [[package]] name = "asttokens" version = "2.0.8" @@ -116,7 +105,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> name = "babel" version = "2.10.3" description = "Internationalization utilities" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -146,17 +135,6 @@ soupsieve = ">1.2" html5lib = ["html5lib"] lxml = ["lxml"] -[[package]] -name = "binaryornot" -version = "0.4.4" -description = "Ultra-lightweight pure Python package to check if a file is binary or text." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -chardet = ">=3.0.2" - [[package]] name = "black" version = "22.6.0" @@ -199,7 +177,7 @@ dev = ["build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "pip-tools (= name = "certifi" version = "2022.6.15" description = "Python package for providing Mozilla's CA Bundle." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -214,19 +192,11 @@ python-versions = "*" [package.dependencies] pycparser = "*" -[[package]] -name = "chardet" -version = "5.0.0" -description = "Universal encoding detector for Python 3" -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" +category = "dev" optional = false python-versions = ">=3.6.0" @@ -252,39 +222,11 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "cookiecutter" -version = "2.1.1" -description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -binaryornot = ">=0.4.4" -click = ">=7.0,<9.0.0" -Jinja2 = ">=2.7,<4.0.0" -jinja2-time = ">=0.2.0" -python-slugify = ">=4.0.0" -pyyaml = ">=5.3.1" -requests = ">=2.23.0" - -[[package]] -name = "cookiecutter-poetry" -version = "0.3.7" -description = "A python cookiecutter application to create a new python project that uses poetry to manage its dependencies." -category = "main" -optional = false -python-versions = ">=3.8,<3.11" - -[package.dependencies] -cookiecutter = ">=2.1.1,<3.0.0" - [[package]] name = "debugpy" version = "1.6.3" description = "An implementation of the Debug Adapter Protocol for Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -362,7 +304,7 @@ dev = ["twine", "markdown", "flake8", "wheel"] name = "idna" version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" +category = "dev" optional = false python-versions = ">=3.5" @@ -409,7 +351,7 @@ python-versions = "*" name = "ipykernel" version = "6.15.2" description = "IPython Kernel for Jupyter" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -468,7 +410,7 @@ test_extra = ["pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotli name = "ipython-genutils" version = "0.2.0" description = "Vestigial utilities from IPython" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -515,23 +457,11 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "jinja2-time" -version = "0.2.0" -description = "Jinja2 Extension for Dates and Times" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -arrow = "*" -jinja2 = "*" - [[package]] name = "json5" version = "0.9.10" description = "A Python implementation of the JSON5 data format." -category = "main" +category = "dev" optional = false python-versions = "*" @@ -596,7 +526,7 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] name = "jupyter-server" version = "1.18.1" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -625,7 +555,7 @@ test = ["coverage", "ipykernel", "pre-commit", "pytest-console-scripts", "pytest name = "jupyterlab" version = "3.4.5" description = "JupyterLab computational environment" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -656,7 +586,7 @@ python-versions = ">=3.7" name = "jupyterlab-server" version = "2.15.1" description = "A set of server components for JupyterLab and JupyterLab like applications." -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -868,7 +798,7 @@ python-versions = "*" name = "nbclassic" version = "0.4.3" description = "A web-based notebook environment for interactive computing" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -979,7 +909,7 @@ python-versions = ">=3.5" name = "notebook" version = "6.4.12" description = "A web-based notebook environment for interactive computing" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -1009,7 +939,7 @@ test = ["pytest", "coverage", "requests", "testpath", "nbval", "selenium", "pyte name = "notebook-shim" version = "0.1.0" description = "A shim layer for notebook traits and config" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -1019,14 +949,6 @@ jupyter-server = ">=1.8,<2.0" [package.extras] test = ["pytest", "pytest-tornasync", "pytest-console-scripts"] -[[package]] -name = "numpy" -version = "1.23.2" -description = "NumPy is the fundamental package for array computing with Python." -category = "main" -optional = false -python-versions = ">=3.8" - [[package]] name = "packaging" version = "21.3" @@ -1038,27 +960,6 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" -[[package]] -name = "pandas" -version = "1.4.3" -description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -numpy = [ - {version = ">=1.18.5", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""}, - {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""}, - {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, -] -python-dateutil = ">=2.8.1" -pytz = ">=2020.1" - -[package.extras] -test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] - [[package]] name = "pandocfilters" version = "1.5.0" @@ -1142,7 +1043,7 @@ testing = ["pytest", "pytest-benchmark"] name = "prometheus-client" version = "0.14.1" description = "Python client for the Prometheus monitoring system." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -1164,7 +1065,7 @@ wcwidth = "*" name = "psutil" version = "5.9.1" description = "Cross-platform lib for process and system monitoring in Python." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -1295,20 +1196,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" -[[package]] -name = "python-slugify" -version = "6.1.2" -description = "A Python slugify application that also handles Unicode" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.dependencies] -text-unidecode = ">=1.3" - -[package.extras] -unidecode = ["Unidecode (>=1.1.1)"] - [[package]] name = "pytkdocs" version = "0.16.1" @@ -1327,7 +1214,7 @@ numpy-style = ["docstring_parser (>=0.7)"] name = "pytz" version = "2022.2.1" description = "World timezone definitions, modern and historical" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -1343,7 +1230,7 @@ python-versions = "*" name = "pywinpty" version = "2.0.7" description = "Pseudo terminal support for Windows from Python." -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -1351,7 +1238,7 @@ python-versions = ">=3.7" name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -1382,7 +1269,7 @@ py = {version = "*", markers = "implementation_name == \"pypy\""} name = "requests" version = "2.28.1" description = "Python HTTP for Humans." -category = "main" +category = "dev" optional = false python-versions = ">=3.7, <4" @@ -1400,7 +1287,7 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] name = "send2trash" version = "1.8.0" description = "Send file to trash natively under Mac OS X, Windows and Linux." -category = "main" +category = "dev" optional = false python-versions = "*" @@ -1419,11 +1306,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "sniffio" -version = "1.2.0" +version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" +category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [[package]] name = "soupsieve" @@ -1453,7 +1340,7 @@ tests = ["cython", "littleutils", "pygments", "typeguard", "pytest"] name = "terminado" version = "0.15.0" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -1465,14 +1352,6 @@ tornado = ">=6.1.0" [package.extras] test = ["pre-commit", "pytest-timeout", "pytest (>=6.0)"] -[[package]] -name = "text-unidecode" -version = "1.3" -description = "The most basic Text::Unidecode port" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "tinycss2" version = "1.1.1" @@ -1535,7 +1414,7 @@ python-versions = ">=3.7" name = "urllib3" version = "1.26.12" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" @@ -1575,7 +1454,7 @@ python-versions = "*" name = "websocket-client" version = "1.4.0" description = "WebSocket client for Python with low level API options" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -1599,7 +1478,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = ">=3.8,<3.11" -content-hash = "58227726d10832a2b54f8b67e46f803ae500b298436c0478d0e88f92124055b4" +content-hash = "9a9e37ecc6ef583a5e8a483fbfe84d0131020c4d5206fbfb4636a93abf8aa6b9" [metadata.files] anyio = [ @@ -1637,10 +1516,6 @@ argon2-cffi-bindings = [ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, ] -arrow = [ - {file = "arrow-1.2.2-py3-none-any.whl", hash = "sha256:d622c46ca681b5b3e3574fcb60a04e5cc81b9625112d5fb2b44220c36c892177"}, - {file = "arrow-1.2.2.tar.gz", hash = "sha256:05caf1fd3d9a11a1135b2b6f09887421153b94558e5ef4d090b567b47173ac2b"}, -] asttokens = [] astunparse = [ {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, @@ -1660,10 +1535,6 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, ] -binaryornot = [ - {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, - {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, -] black = [ {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"}, {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"}, @@ -1763,7 +1634,6 @@ cffi = [ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] -chardet = [] charset-normalizer = [] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, @@ -1773,11 +1643,6 @@ colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] -cookiecutter = [ - {file = "cookiecutter-2.1.1-py2.py3-none-any.whl", hash = "sha256:9f3ab027cec4f70916e28f03470bdb41e637a3ad354b4d65c765d93aad160022"}, - {file = "cookiecutter-2.1.1.tar.gz", hash = "sha256:f3982be8d9c53dac1261864013fdec7f83afd2e42ede6f6dd069c5e149c540d5"}, -] -cookiecutter-poetry = [] debugpy = [] decorator = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, @@ -1835,10 +1700,6 @@ jinja2 = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] -jinja2-time = [ - {file = "jinja2-time-0.2.0.tar.gz", hash = "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40"}, - {file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"}, -] json5 = [] jsonschema = [] jupyter-client = [] @@ -1976,34 +1837,10 @@ notebook-shim = [ {file = "notebook_shim-0.1.0-py3-none-any.whl", hash = "sha256:02432d55a01139ac16e2100888aa2b56c614720cec73a27e71f40a5387e45324"}, {file = "notebook_shim-0.1.0.tar.gz", hash = "sha256:7897e47a36d92248925a2143e3596f19c60597708f7bef50d81fcd31d7263e85"}, ] -numpy = [] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -pandas = [ - {file = "pandas-1.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640"}, - {file = "pandas-1.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5"}, - {file = "pandas-1.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f"}, - {file = "pandas-1.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112"}, - {file = "pandas-1.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230"}, - {file = "pandas-1.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1"}, - {file = "pandas-1.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81"}, - {file = "pandas-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318"}, - {file = "pandas-1.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef"}, - {file = "pandas-1.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92"}, - {file = "pandas-1.4.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0"}, - {file = "pandas-1.4.3-cp38-cp38-win32.whl", hash = "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d"}, - {file = "pandas-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e"}, - {file = "pandas-1.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4"}, - {file = "pandas-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6"}, - {file = "pandas-1.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84"}, - {file = "pandas-1.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf"}, - {file = "pandas-1.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76"}, - {file = "pandas-1.4.3-cp39-cp39-win32.whl", hash = "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d"}, - {file = "pandas-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e"}, - {file = "pandas-1.4.3.tar.gz", hash = "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c"}, -] pandocfilters = [ {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, @@ -2136,7 +1973,6 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-slugify = [] pytkdocs = [ {file = "pytkdocs-0.16.1-py3-none-any.whl", hash = "sha256:a8c3f46ecef0b92864cc598e9101e9c4cf832ebbf228f50c84aa5dd850aac379"}, {file = "pytkdocs-0.16.1.tar.gz", hash = "sha256:e2ccf6dfe9dbbceb09818673f040f1a7c32ed0bffb2d709b06be6453c4026045"}, @@ -2211,10 +2047,7 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -sniffio = [ - {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, - {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, -] +sniffio = [] soupsieve = [ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, @@ -2224,10 +2057,6 @@ terminado = [ {file = "terminado-0.15.0-py3-none-any.whl", hash = "sha256:0d5f126fbfdb5887b25ae7d9d07b0d716b1cc0ccaacc71c1f3c14d228e065197"}, {file = "terminado-0.15.0.tar.gz", hash = "sha256:ab4eeedccfcc1e6134bfee86106af90852c69d602884ea3a1e8ca6d4486e9bfe"}, ] -text-unidecode = [ - {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, - {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, -] tinycss2 = [ {file = "tinycss2-1.1.1-py3-none-any.whl", hash = "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8"}, {file = "tinycss2-1.1.1.tar.gz", hash = "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf"}, diff --git a/pyproject.toml b/pyproject.toml index 1e629c59..2119ade6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,6 @@ toml = "^0.10.2" isort = "^5.10.1" click = "^8.1.3" - [tool.poetry.dev-dependencies] black = "^22.6.0" flake8 = "^4.0.1" @@ -25,6 +24,7 @@ mkdocs = "^1.3.0" mkdocs-material = "^8.3.8" mkdocstrings = "^0.18.1" mypy = "^0.961" +jupyterlab = "^3.4.5" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/data/projects/project_with_obsolete/README.md b/tests/data/projects/project_with_obsolete/README.md index 4ddfc48d..05b067be 100644 --- a/tests/data/projects/project_with_obsolete/README.md +++ b/tests/data/projects/project_with_obsolete/README.md @@ -1,7 +1,42 @@ -Imported are pandas and numpy -Dependencies are pandas, numpy, toml, cookiecutter-poetry, isort and click -pyproject.toml specifies to ignore the dependency toml +Dependencies are: -So expected output for obsolete packages: +``` +pandas +numpy +toml +cookiecutter-poetry +isort +click +``` -cookiecutter-poetry, isort, click. +Imported in .py files are + +``` +pandas +numpy +click +``` + +Additional imports in .ipynb file: + +``` +cookiecutter_poetry +``` + +pyproject.toml specifies to ignore the dependency: + +``` +toml +``` + +So expected output for obsolete packages when ignoring ipynb: + +``` +cookiecutter-poetry +isort +``` +Expected output for obsolete packages when including ipynb files: + +``` +isort +``` diff --git a/tests/data/projects/project_with_obsolete/src/main.py b/tests/data/projects/project_with_obsolete/src/main.py index d318b734..0fa8cdef 100644 --- a/tests/data/projects/project_with_obsolete/src/main.py +++ b/tests/data/projects/project_with_obsolete/src/main.py @@ -1,7 +1,6 @@ from os import chdir, walk from pathlib import Path -from typing import List -import numpy as np -import pandas +import click +import pandas as pd from numpy.random import sample diff --git a/tests/data/projects/project_with_obsolete/src/notebook.ipynb b/tests/data/projects/project_with_obsolete/src/notebook.ipynb new file mode 100644 index 00000000..d4af4c1b --- /dev/null +++ b/tests/data/projects/project_with_obsolete/src/notebook.ipynb @@ -0,0 +1,38 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "9f4924ec-2200-4801-9d49-d4833651cbc4", + "metadata": {}, + "outputs": [], + "source": [ + "import click\n", + "import pandas as pd\n", + "from numpy.random import sample\n", + "import cookiecutter_poetry" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/test_cli.py b/tests/test_cli.py index 21e34a9f..7561a5a0 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -19,8 +19,8 @@ def run_within_dir(path: str): def test_cli_returns_error(tmp_path): """ - data/projects/project_with_obsolete has obsolete dependencies. Verify that `deptry check` returns status code 1 - and verify that it finds the right obsolete dependencies. + data/projects/project_with_obsolete has obsolete dependencies. + Verify that `deptry check` returns status code 1 and verify that it finds the right obsolete dependencies. """ tmp_path_proj = tmp_path / "project_with_obsolete" @@ -30,10 +30,13 @@ def test_cli_returns_error(tmp_path): subprocess.check_call(shlex.split("poetry install --no-interaction --no-root")) == 0 result = subprocess.run(shlex.split("poetry run deptry check"), capture_output=True, text=True) assert result.returncode == 1 - assert ( - result.stderr - == "pyproject.toml contains obsolete dependencies: ['click', 'cookiecutter-poetry', 'isort']\n" + assert result.stderr == "pyproject.toml contains obsolete dependencies: ['isort']\n" + + result = subprocess.run( + shlex.split("poetry run deptry check --ignore-notebooks"), capture_output=True, text=True ) + assert result.returncode == 1 + assert result.stderr == "pyproject.toml contains obsolete dependencies: ['cookiecutter-poetry', 'isort']\n" def test_cli_returns_no_error(tmp_path): @@ -54,8 +57,8 @@ def test_cli_returns_no_error(tmp_path): def test_cli_argument_overwrites_pyproject_toml_argument(tmp_path): """ The cli argument should overwrite the pyproject.toml argument. In project_with_obsolete, pyproject.toml specifies - to ignore 'toml' and the obsolete dependencies are ['click', 'cookiecutter-poetry', 'isort']. - Verify that this is changed to ['cookiecutter-poetry', 'isort', 'toml'] if we run the command with `-i click` + to ignore 'toml' and the obsolete dependencies are ['click', 'isort']. + Verify that this is changed to ['isort', 'toml'] if we run the command with `-i click` """ tmp_path_proj = tmp_path / "project_with_obsolete" @@ -65,9 +68,7 @@ def test_cli_argument_overwrites_pyproject_toml_argument(tmp_path): subprocess.check_call(shlex.split("poetry install --no-interaction --no-root")) == 0 result = subprocess.run(shlex.split("poetry run deptry check -i click"), capture_output=True, text=True) assert result.returncode == 1 - assert ( - result.stderr == "pyproject.toml contains obsolete dependencies: ['cookiecutter-poetry', 'isort', 'toml']\n" - ) + assert result.stderr == "pyproject.toml contains obsolete dependencies: ['isort', 'toml']\n" def test_cli_help(): diff --git a/tests/test_import_parser.py b/tests/test_import_parser.py index 4d0e9890..1750975c 100644 --- a/tests/test_import_parser.py +++ b/tests/test_import_parser.py @@ -4,6 +4,13 @@ from deptry.import_parser import ImportParser -def test_import_parser(): - imported_modules = ImportParser().get_imported_modules_for_file(Path("tests/data/some_imports.py")) +def test_import_parser_py(): + imported_modules = ImportParser()._get_imported_modules_from_file(Path("tests/data/some_imports.py")) assert set(imported_modules) == set(["os", "pathlib", "typing", "pandas", "numpy"]) + + +def test_import_parser_ipynb(): + imported_modules = ImportParser()._get_imported_modules_from_file( + Path("tests/data/projects/project_with_obsolete/src/notebook.ipynb") + ) + assert set(imported_modules) == set(["click", "pandas", "numpy", "cookiecutter_poetry"]) diff --git a/tests/test_notebook_import_extractor.py b/tests/test_notebook_import_extractor.py new file mode 100644 index 00000000..8b716e07 --- /dev/null +++ b/tests/test_notebook_import_extractor.py @@ -0,0 +1,14 @@ +import os +from contextlib import contextmanager +from pathlib import Path + +import pytest + +from deptry.notebook_import_extractor import NotebookImportExtractor + + +def test_convert_notebook(): + imports = NotebookImportExtractor().extract("tests/data/projects/project_with_obsolete/src/notebook.ipynb") + assert "import click" in imports[0] + assert "import pandas as pd" in imports[1] + assert len(imports) == 4 diff --git a/tests/test_python_file_finder.py b/tests/test_python_file_finder.py new file mode 100644 index 00000000..3c2784fa --- /dev/null +++ b/tests/test_python_file_finder.py @@ -0,0 +1,33 @@ +import os +import shlex +import shutil +import subprocess +from contextlib import contextmanager +from pathlib import Path + +import pytest + +from deptry.python_file_finder import PythonFileFinder + + +def test_find_only_py_files(): + """ + Should only find src/main.py + """ + files = PythonFileFinder(ignore_notebooks=True).get_all_python_files_in( + Path("tests/data/projects/project_with_obsolete") + ) + assert len(files) == 1 + assert "main.py" in (str(files[0])) + + +def test_find_py_and_ipynb_files(): + """ + Should find src/main.py and src/notebook.ipynb + """ + files = PythonFileFinder(ignore_notebooks=False).get_all_python_files_in( + Path("tests/data/projects/project_with_obsolete") + ) + assert len(files) == 2 + assert any(["main.py" in str(x) for x in files]) + assert any(["notebook.ipynb" in str(x) for x in files])