Skip to content

Commit

Permalink
#432 Use importlib.metadata.version to get the version (#502)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Adi Roiban <adi.roiban@chevah.com>
Co-authored-by: Adi Roiban <adiroiban@gmail.com>
Co-authored-by: Chris Beaven <smileychris@gmail.com>
  • Loading branch information
5 people authored May 22, 2024
1 parent 2bde077 commit 6d96010
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 11 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ requires-python = ">=3.8"
dependencies = [
"click",
"importlib-resources>=5; python_version<'3.10'",
"importlib-metadata>=4.6; python_version<'3.10'",
"incremental",
"jinja2",
"tomli; python_version<'3.11'",
Expand Down
38 changes: 36 additions & 2 deletions src/towncrier/_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@
Responsible for getting the version and name from a project.
"""


from __future__ import annotations

import sys

from importlib import import_module
from importlib.metadata import version as metadata_version
from types import ModuleType
from typing import Any

from incremental import Version as IncrementalVersion


if sys.version_info >= (3, 10):
from importlib.metadata import packages_distributions
else:
from importlib_metadata import packages_distributions # type: ignore


def _get_package(package_dir: str, package: str) -> ModuleType:
try:
module = import_module(package)
Expand All @@ -37,13 +44,40 @@ def _get_package(package_dir: str, package: str) -> ModuleType:
return module


def _get_metadata_version(package: str) -> str | None:
"""
Try to get the version from the package metadata.
"""
distributions = packages_distributions()
distribution_names = distributions.get(package)
if not distribution_names or len(distribution_names) != 1:
# We can only determine the version if there is exactly one matching distribution.
return None
return metadata_version(distribution_names[0])


def get_version(package_dir: str, package: str) -> str:
"""
Get the version of a package.
Try to extract the version from the distribution version metadata that matches
`package`, then fall back to looking for the package in `package_dir`.
"""
version: Any

# First try to get the version from the package metadata.
if version := _get_metadata_version(package):
return version

# When no version if found, fall back to looking for the package in `package_dir`.
module = _get_package(package_dir, package)

version = getattr(module, "__version__", None)

if not version:
raise Exception("No __version__, I don't know how else to look")
raise Exception(
f"No __version__ or metadata version info for the '{package}' package."
)

if isinstance(version, str):
return version.strip()
Expand Down
1 change: 1 addition & 0 deletions src/towncrier/newsfragments/432.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Inferring the version of a Python package now tries to use the metadata of the installed package before importing the package explicitly (which only looks for ``[package].__version__``).
29 changes: 20 additions & 9 deletions src/towncrier/test/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import os
import sys

from unittest import skipIf
from importlib.metadata import version as metadata_version
from unittest import mock

from click.testing import CliRunner
from twisted.trial.unittest import TestCase
Expand All @@ -14,12 +15,6 @@
from .helpers import write


try:
from importlib.metadata import version as metadata_version
except ImportError:
metadata_version = None


towncrier_cli.name = "towncrier"


Expand Down Expand Up @@ -50,7 +45,6 @@ def test_tuple(self):
version = get_version(temp, "mytestproja")
self.assertEqual(version, "1.3.12")

@skipIf(metadata_version is None, "Needs importlib.metadata.")
def test_incremental(self):
"""
An incremental Version __version__ is picked up.
Expand All @@ -60,6 +54,14 @@ def test_incremental(self):
with self.assertWarnsRegex(
DeprecationWarning, "Accessing towncrier.__version__ is deprecated.*"
):
# Previously this triggered towncrier.__version__ but now the first version
# check is from the package metadata. Let's mock out that part to ensure we
# can get incremental versions from __version__ still.
with mock.patch(
"towncrier._project._get_metadata_version", return_value=None
):
version = get_version(pkg, "towncrier")

version = get_version(pkg, "towncrier")

with self.assertWarnsRegex(
Expand All @@ -70,6 +72,13 @@ def test_incremental(self):
self.assertEqual(metadata_version("towncrier"), version)
self.assertEqual("towncrier", name)

def test_version_from_metadata(self):
"""
A version from package metadata is picked up.
"""
version = get_version(".", "towncrier")
self.assertEqual(metadata_version("towncrier"), version)

def _setup_missing(self):
"""
Create a minimalistic project with missing metadata in a temporary
Expand All @@ -91,10 +100,12 @@ def test_missing_version(self):
tmp_dir = self._setup_missing()

with self.assertRaises(Exception) as e:
# The 'missing' package has no __version__ string.
get_version(tmp_dir, "missing")

self.assertEqual(
("No __version__, I don't know how else to look",), e.exception.args
("No __version__ or metadata version info for the 'missing' package.",),
e.exception.args,
)

def test_missing_version_project_name(self):
Expand Down

0 comments on commit 6d96010

Please sign in to comment.