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
4 changes: 4 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
python -m pip install --upgrade pip
python -m pip install testtools
python -m pip install ruff
python -m pip install mypy
- name: Test with testtools
run: |
python -m testtools.run extras.tests.test_suite
Expand All @@ -33,3 +34,6 @@ jobs:
- name: Check formatting with ruff
run: |
python -m ruff format --check .
- name: Type check with mypy
run: |
python -m mypy --strict --ignore-missing-imports --follow-imports=skip --exclude tests .
19 changes: 14 additions & 5 deletions extras/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""Extensions to the Python standard library."""

import sys
from typing import Optional, Callable, Sequence

__all__ = [
"try_import",
Expand All @@ -24,7 +25,11 @@
__version__ = (1, 0, 0, "final", 0)


def try_import(name, alternative=None, error_callback=None):
def try_import(
name: str,
alternative: Optional[object] = None,
error_callback: Optional[Callable[[ImportError], None]] = None,
) -> object:
"""Attempt to import ``name``. If it fails, return ``alternative``.

When supporting multiple versions of Python or optional dependencies, it
Expand All @@ -38,7 +43,7 @@ def try_import(name, alternative=None, error_callback=None):
when the module cannot be loaded.
"""
module_segments = name.split(".")
last_error = None
last_error: Optional[ImportError] = None
remainder = []
# module_name will be what successfully imports. We cannot walk from the
# __import__ result because in import loops (A imports A.B, which imports
Expand All @@ -48,7 +53,7 @@ def try_import(name, alternative=None, error_callback=None):
try:
__import__(module_name)
except ImportError:
last_error = sys.exc_info()[1]
last_error = sys.exc_info()[1] # type: ignore
remainder.append(module_segments.pop())
continue
else:
Expand All @@ -60,7 +65,7 @@ def try_import(name, alternative=None, error_callback=None):
module = sys.modules[module_name]
nonexistent = object()
for segment in reversed(remainder):
module = getattr(module, segment, nonexistent)
module = getattr(module, segment, nonexistent) # type: ignore
if module is nonexistent:
if last_error is not None and error_callback is not None:
error_callback(last_error)
Expand All @@ -71,7 +76,11 @@ def try_import(name, alternative=None, error_callback=None):
_RAISE_EXCEPTION = object()


def try_imports(module_names, alternative=_RAISE_EXCEPTION, error_callback=None):
def try_imports(
module_names: Sequence[str],
alternative: object = _RAISE_EXCEPTION,
error_callback: Optional[Callable[[ImportError], None]] = None,
) -> object:
"""Attempt to import modules.

Tries to import the first module in ``module_names``. If it can be
Expand Down
Empty file added extras/py.typed
Empty file.
17 changes: 10 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""Distutils installer for extras."""

from setuptools import setup
from setuptools import setup, Command
import os.path
from typing import cast

import extras

testtools_cmd = extras.try_import("testtools.TestCommand")
testtools_cmd = cast(Command, extras.try_import("testtools.TestCommand"))


def get_version():
def get_version() -> str:
"""Return the version of extras that we are building."""
version = ".".join(str(component) for component in extras.__version__[0:3])
return version


def get_long_description():
def get_long_description() -> str:
readme_path = os.path.join(os.path.dirname(__file__), "README.rst")
return open(readme_path).read()
with open(readme_path) as f:
return f.read()


cmdclass = {}
cmdclass: dict[str, type[Command]] = {}

if testtools_cmd is not None:
cmdclass["test"] = testtools_cmd
Expand Down Expand Up @@ -53,5 +55,6 @@ def get_long_description():
"extras",
"extras.tests",
],
package_data={"extras": ["py.typed"]},
cmdclass=cmdclass,
)