Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
run: "python -m tox"

- name: "Report to coveralls"
# coverage is only created in the py39 environment
# coverage is only created in the py314 environment
# --service=github is a workaround for bug
# https://github.com/coveralls-clients/coveralls-python/issues/251
if: "matrix.python-version == '3.14'"
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pip-wheel-metadata

# Virtualenv
/env/
/src/
/.venv/

# Unit test / coverage reports
.cache
Expand All @@ -20,4 +20,8 @@ htmlcov
.tox

# Sphinx documentation
/doc/_build/
/doc/build/

# Type checkers
.mypy_cache/
12 changes: 10 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
CHANGES
*******

0.3 (unreleased)
----------------
1.0.0 (unreleased)
------------------

- Skip importing packages in scan() since they're already imported in walk_packages().

- Add type annotations.

- Add support for Python 3.10 - 3.14.

Expand All @@ -12,6 +16,10 @@ CHANGES

- Use GitHub Actions for CI.

- Add tests for 100% coverage.

- Update docs.


0.2 (2020-01-29)
----------------
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ import a package and its sub-modules and sub-packages.

Documentation_.

.. _Documentation: http://importscan.readthedocs.org
.. _Documentation: https://importscan.readthedocs.org
5 changes: 1 addition & 4 deletions develop_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
# development
-e '.[test,coverage,lint]'
-e '.[test,coverage,lint,docs,mypy,pyright]'
pre-commit
tox >= 4
radon

# documentation
sphinx

# releaser
zest.releaser[recommended]
9 changes: 9 additions & 0 deletions doc/_build/doctest/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Results of doctest builder run on 2026-08-07 09:26:02
=====================================================

Doctest summary
===============
0 tests
0 failures in tests
0 failures in setup code
0 failures in cleanup code
Binary file not shown.
Binary file not shown.
Binary file added doc/_build/doctrees/api.doctree
Binary file not shown.
Binary file added doc/_build/doctrees/changes.doctree
Binary file not shown.
Binary file added doc/_build/doctrees/developing.doctree
Binary file not shown.
Binary file added doc/_build/doctrees/environment.pickle
Binary file not shown.
Binary file added doc/_build/doctrees/index.doctree
Binary file not shown.
47 changes: 36 additions & 11 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# All configuration values have a default; values that are commented out
# serve to show the default.

import pkg_resources
from importlib import metadata

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
Expand All @@ -24,16 +24,20 @@

# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx"]
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.doctest",
]

autoclass_content = "both"

autodoc_member_order = "groupwise"

intersphinx_mapping = {
"reg": ("http://reg.readthedocs.io/en/latest", None),
"webob": ("http://docs.webob.org/en/latest", None),
"bowerstatic": ("http://bowerstatic.readthedocs.io/en/latest", None),
"reg": ("https://reg.readthedocs.io/en/latest", None),
"webob": ("https://docs.pylonsproject.org/projects/webob/en/latest", None),
"bowerstatic": ("https://bowerstatic.readthedocs.io/en/latest", None),
}

# Add any paths that contain templates here, relative to this directory.
Expand All @@ -60,7 +64,28 @@
# built documents.
#
# The short X.Y version.
version = pkg_resources.get_distribution("importscan").version
try:
version = metadata.version("importscan")
except metadata.PackageNotFoundError:
# Fallback for ReadTheDocs and other environments where the package isn't installed
# Try to get version from pyproject.toml
import os
import re

try:
pyproject_path = os.path.join(
os.path.dirname(__file__), "..", "pyproject.toml"
)
with open(pyproject_path) as f:
content = f.read()
# Simple regex to extract version
version_match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
if version_match:
version = version_match.group(1)
else:
version = "0.0.0"
except (FileNotFoundError, Exception):
version = "0.0.0"
# The full version, including alpha/beta/rc tags.
release = version

Expand All @@ -69,7 +94,7 @@
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
Expand All @@ -82,7 +107,7 @@
exclude_patterns = ["_build"]

# The reST default role (used for this markup: `text`) to use for all
# documents.
# documents.n
# default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
Expand Down Expand Up @@ -142,7 +167,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = []
html_static_path: list[str] = []

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
Expand Down Expand Up @@ -209,7 +234,7 @@

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
latex_elements: dict[str, str] = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
Expand Down Expand Up @@ -297,4 +322,4 @@


# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"https://docs.python.org/": None}
# intersphinx_mapping = {"https://docs.python.org/": None}
27 changes: 11 additions & 16 deletions doc/developing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Install Importscan for development

Clone Importscan from github::

$ git clone git@github.com:faassen/importscan.git
$ git clone git@github.com:morepath/importscan.git

If this doesn't work and you get an error 'Permission denied (publickey)',
you need to upload your ssh public key to github_.
Expand All @@ -21,15 +21,11 @@ Make sure you have virtualenv_ installed.

Create a new virtualenv for Python 3 inside the importscan directory::

$ virtualenv -p python3 env/py3
$ python -m venv --upgrade-deps .venv

Activate the virtualenv::

$ source env/py3/bin/activate

Make sure you have recent setuptools and pip installed::

$ pip install -U setuptools pip
$ source .venv/bin/activate

Install the various dependencies and development tools from
develop_requirements.txt::
Expand All @@ -42,9 +38,8 @@ For upgrading the requirements just run the command again.

The following commands work only if you have the virtualenv activated.

.. _github: https://help.github.com/articles/generating-an-ssh-key

.. _virtualenv: https://pypi.python.org/pypi/virtualenv
.. _github: https://docs.github.com/en/authentication/connecting-to-github-with-ssh
.. _virtualenv: https://pypi.org/project/virtualenv

Install pre-commit hook for Black integration
---------------------------------------------
Expand All @@ -54,7 +49,7 @@ install the `pre-commit hook`_ for Black integration before committing::

$ pre-commit install

.. _`pre-commit hook`: https://black.readthedocs.io/en/stable/version_control_integration.html
.. _`pre-commit hook`: https://black.readthedocs.io/en/stable/integrations/source_version_control.html

Running the tests
-----------------
Expand All @@ -71,7 +66,7 @@ You can then point your web browser to the ``htmlcov/index.html`` file
in the project directory and click on modules to see detailed coverage
information.

.. _`py.test`: http://pytest.org/latest/
.. _`py.test`: https://pytest.org/latest/

Black
-----
Expand Down Expand Up @@ -115,11 +110,11 @@ To also show cyclomatic complexity, use this command::

$ flake8 --max-complexity=10 importscan

.. _flake8: https://pypi.python.org/pypi/flake8
.. _flake8: https://pypi.org/project/flake8

.. _pyflakes: https://pypi.python.org/pypi/pyflakes
.. _pyflakes: https://pypi.org/project/pyflakes

.. _pep8: http://www.python.org/dev/peps/pep-0008/
.. _pep8: https://peps.python.org/pep-0008

.. _`cyclomatic complexity`: https://en.wikipedia.org/wiki/Cyclomatic_complexity

Expand Down Expand Up @@ -151,4 +146,4 @@ You can also specify a test environment to run e.g.::
$ tox -e lint
$ tox -e coverage

.. _pyenv: https://github.com/yyuu/pyenv
.. _pyenv: https://github.com/pyenv/pyenv
34 changes: 21 additions & 13 deletions importscan/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import sys
from pkgutil import iter_modules
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast

if TYPE_CHECKING:
from collections.abc import Callable, Generator, Iterable
from importlib.abc import Loader
from types import ModuleType
from typing_extensions import TypeIs

from importscan.types import IgnoreModule, ModuleInfo, StrOrBytesPath


Expand Down Expand Up @@ -119,37 +120,44 @@ def handle_error(name, e):
# into iter_modules for submodules/subpackages. There's also
# the additional issue that not all finders will implement the
# non-standard iter_modules method, but without it there's
# no way to list all of the modules. Also since walk_packages
# already imports all of the packages, why are we importing
# them again here? Shouldn't we only import modules here?
# Also why do we do only use `import_module` here, but not
# in `walk_packages`? Doesn't that mean that the additional
# check in `import_module` doesn't do anything for packages?
loader = importer.find_spec(modname).loader # type: ignore
# no way to list all of the modules.
#
# Note: Packages are already imported in walk_packages() to access
# their __path__ for recursion and have already been error-checked.
# Only modules need to be imported here.
if ispkg:
continue

spec = importer.find_spec(modname, None)
loader = spec.loader if spec else None
assert loader is not None

try:
import_module(modname, loader, handle_error)
finally:
if hasattr(loader, "file") and hasattr(
loader.file, # pyright: ignore[reportAttributeAccessIssue]
getattr(loader, "file"),
"close",
):
loader.file.close() # pyright: ignore[reportAttributeAccessIssue]
getattr(loader, "file").close()


def import_module(
modname: str,
loader: Loader,
handle_error: Callable[[str, Exception], object] | None,
) -> None:
get_filename = getattr(loader, "get_filename", None)
get_filename: Callable[..., str] | None = getattr(
loader, "get_filename", None
)
if get_filename is None:
get_filename = loader._get_filename # type: ignore[attr-defined]
try:
fn = get_filename(modname)
fn: str = cast(
str, get_filename(modname) # pyright: ignore[reportOptionalCall]
)
except TypeError:
fn = get_filename()
fn = cast(str, get_filename()) # pyright: ignore[reportOptionalCall]
# only scan non-orphaned source files and package directories
if fn.endswith((".pyc", ".pyo", "$py.class")):
return
Expand Down
1 change: 1 addition & 0 deletions importscan/tests/fixtures/importerror_pkg/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# pkg
1 change: 1 addition & 0 deletions importscan/tests/fixtures/importerror_pkg/sub/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
raise ImportError("cannot import sub")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# pkg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
raise ImportError("cannot import sub")
Loading