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
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
/.eggs/
*egg-info/

# setuptools_scm
/src/*/_version.py

# Vi
*.swp

Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,13 @@ FUNCTIONS
leading underscore!
:param func: The function to turn into a property

get_version(java_class)
Return the version of a Java class.
Requires org.scijava:scijava-common on the classpath.
get_version(java_class_or_python_package)
Return the version of a Java class or Python package.

For Python packages, uses importlib.metadata.version if available
(Python 3.8+), with pkg_resources.get_distribution as a fallback.

For Java classes, requires org.scijava:scijava-common on the classpath.

The version string is extracted from the given class's associated JAR
artifact (if any), either the embedded Maven POM if the project was built
Expand All @@ -182,12 +186,12 @@ FUNCTIONS
jimport a java.awt or javax.swing class. This can lead to deadlocks
on macOS if you are not running in headless mode and did not invoke
those actions via the jpype.setupGuiEnvironment wrapper function;
see the Troubleshooting section below for details.
see the Troubleshooting section of the scyjava README for details.

is_jvm_headless()
Return true iff Java is running in headless mode.

:raises RuntimeException: If the JVM has not started yet.
:raises RuntimeError: If the JVM has not started yet.

is_version_at_least(actual_version, minimum_version)
Return a boolean on a version comparison.
Expand Down
2 changes: 1 addition & 1 deletion dev-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies:
- flake8
- pytest
- pytest-cov
- setuptools-scm >= 6.2
- toml
# Project from source
- pip
- pip:
Expand Down
7 changes: 2 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "scyjava"
version = "1.5.2.dev0"
authors = [
{name = "Curtis Rueden", email = "ctrueden@wisc.edu"},
{name = "Philipp Hanslovsky"},
Expand Down Expand Up @@ -38,8 +39,6 @@ dependencies = [
"jpype1 >= 1.3.0",
"jgo",
]
dynamic = ["version"]


[project.urls]
Homepage = "https://github.com/scijava/scyjava"
Expand All @@ -58,7 +57,7 @@ dev = [
"pytest-cov",
"numpy",
"pandas",
"setuptools-scm >= 6.2",
"toml",
]

[tool.setuptools]
Expand All @@ -69,5 +68,3 @@ include-package-data = false
[tool.setuptools.packages.find]
where = ["src"]
namespaces = false

[tool.setuptools_scm]
67 changes: 33 additions & 34 deletions src/scyjava/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys
import typing
from functools import lru_cache
from importlib.util import find_spec
from pathlib import Path
from typing import Any, Callable, Dict, NamedTuple

Expand All @@ -25,9 +26,9 @@
JLong,
JShort,
)

import scyjava.config


_logger = logging.getLogger(__name__)

# Set of module properties
Expand Down Expand Up @@ -65,33 +66,6 @@ def __getattr__(name):
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


@constant
def ___version__():
# First pass: use the version output by setuptools_scm
try:
import scyjava.version

return scyjava.version.version
except ImportError:
pass
# Second pass: use importlib.metadata
try:
from importlib.metadata import PackageNotFoundError, version

return version("scyjava")
except ImportError or PackageNotFoundError:
pass
# Third pass: use pkg_resources
try:
from pkg_resources import get_distribution

return get_distribution("scyjava").version
except ImportError:
pass
# Fourth pass: Give up
return "Cannot determine version! Ensure pkg_resources is installed!"


# -- JVM setup --

_startup_callbacks = []
Expand Down Expand Up @@ -369,20 +343,42 @@ def when_jvm_stops(f):
# -- Utility functions --


def get_version(java_class):
def get_version(java_class_or_python_package):
"""
Return the version of a Java class.
Requires org.scijava:scijava-common on the classpath.
Return the version of a Java class or Python package.

For Python package, uses importlib.metadata.version if available
(Python 3.8+), with pkg_resources.get_distribution as a fallback.

For Java classes, requires org.scijava:scijava-common on the classpath.

The version string is extracted from the given class's associated JAR
artifact (if any), either the embedded Maven POM if the project was built
with Maven, or the JAR manifest's Specification-Version value if it exists.

See org.scijava.VersionUtils.getVersion(Class) for further details.
"""
VersionUtils = jimport("org.scijava.util.VersionUtils")
version = VersionUtils.getVersion(java_class)
return version

if isjava(java_class_or_python_package):
# Assume we were given a Java class object.
VersionUtils = jimport("org.scijava.util.VersionUtils")
return str(VersionUtils.getVersion(java_class_or_python_package))

# Assume we were given a Python package name.

if find_spec("importlib.metadata"):
# Fastest, but requires Python 3.8+.
from importlib.metadata import version

return version(java_class_or_python_package)

if find_spec("pkg_resources"):
# Slower, but works on Python 3.7.
from pkg_resources import get_distribution

return get_distribution(java_class_or_python_package).version

raise RuntimeError("Cannot determine version! Is pkg_resources installed?")


def is_version_at_least(actual_version, minimum_version):
Expand Down Expand Up @@ -1158,6 +1154,9 @@ def _pandas_to_table(df):
return table


__version__ = get_version("scyjava")


# -- JVM startup callbacks --

# NB: These must be performed last, because if this class is imported after the
Expand Down
68 changes: 25 additions & 43 deletions tests/test_version.py
Original file line number Diff line number Diff line change
@@ -1,86 +1,68 @@
import os
import sys
import toml

from pathlib import Path
import pytest
import scyjava
from scyjava import get_version

setuptools_file = os.path.join(os.getcwd(), "src", "scyjava", "_version.py")


def _scyjava_version():
def _expected_version():
"""
Get ScyJava's version.
Get the project version from pyproject.toml.
"""
import scyjava

# It's important that we clear the cache here,
# so that we can test different behaviors.
scyjava.___version__.cache_clear()
# Get the version
return scyjava.__version__

pyproject = toml.load(Path(__file__).parents[1] / "pyproject.toml")
return pyproject["project"]["version"]

def test_version_file():
"""Ensures that, ideally, the version from setuptools_scm is used"""
# Get the version from setuptools_scm
from setuptools_scm import get_version

setuptools_version = get_version(write_to="src/scyjava/_version.py")
# Ensure that the version was written to file
assert os.path.isfile(setuptools_file)
# Ensure that scyjava.__version__ matches this.
assert _scyjava_version() == setuptools_version
# Cleanup - remove file
os.remove(setuptools_file)
assert not os.path.isfile(setuptools_file)
def test_version_dunder():
"""
Ensure that the dunder variable matches _expected_version.
"""
assert _expected_version() == scyjava.__version__


@pytest.mark.skipif(sys.version_info < (3, 8), reason="Requires Python >= 3.8")
def test_version_importlib():
"""
Ensures that, with scyjava.version.version unavailable,
importlib.metadata is used next WITH python 3.8+
Ensure that, with scyjava.version.version unavailable,
importlib.metadata is used next WITH python 3.8+.
"""
# Remove scyjava.version
sys.modules["scyjava.version"] = None
# Ensure scyjava.__version__ matches importlib.metadata.version()
from importlib.metadata import version

assert _scyjava_version() == version("scyjava")
assert _expected_version() == get_version("scyjava")


@pytest.mark.skipif(
sys.version_info >= (3, 8), reason="importlib used instead for Python 3.8+"
)
def test_version_pkg_resources():
"""
Ensures that, with scyjava.version.version AND
Ensure that, with scyjava.version.version AND
importlib.metadata unavailable,
pkg_resources is used next.
"""
# Remove scyjava.version
sys.modules["scyjava.version"] = None
# Remove importlib.metadata
sys.modules["importlib.metadata"] = None
# Ensure scyjava.__version__ matches
# pkg_resources.get_distribution().version
from pkg_resources import get_distribution

assert _scyjava_version() == get_distribution("scyjava").version
assert _expected_version() == get_version("scyjava")


def test_version_unvailable():
def test_version_unavailable():
"""
Ensures that no version is returned if none of these
strategies works.
Ensure that an exception is raised if none of these strategies works.
"""
# Remove scyjava.version
sys.modules["scyjava.version"] = None
# Remove importlib.metadata
sys.modules["importlib.metadata"] = None
# Remove pkg_resources
sys.modules["pkg_resources"] = None
# Ensure scyjava.__version__ is an error message.
# Ensure scyjava.__version__ raises an exception.
with pytest.raises(RuntimeError) as e_info:
get_version("scyjava")
assert (
_scyjava_version()
== "Cannot determine version! Ensure pkg_resources is installed!"
)
"RuntimeError: Cannot determine version! Is pkg_resources installed?"
) == e_info.exconly()