Skip to content

Versioning

provide.foundation.utils.versioning

TODO: Add module docstring.

Functions

get_version

get_version(
    package_name: str, caller_file: str | Path | None = None
) -> str

Get the version for a package.

Reads from VERSION file if it exists, otherwise falls back to package metadata, then to default development version.

This function is thread-safe and caches results after the first call per package.

Parameters:

Name Type Description Default
package_name str

The package name as it appears in PyPI (e.g., "provide-foundation")

required
caller_file str | Path | None

Path to the calling module's file, used to find VERSION file. If None, uses the calling context.

None

Returns:

Type Description
str

The current version string

Source code in provide/foundation/utils/versioning.py
def get_version(package_name: str, caller_file: str | Path | None = None) -> str:
    """Get the version for a package.

    Reads from VERSION file if it exists, otherwise falls back to package metadata,
    then to default development version.

    This function is thread-safe and caches results after the first call per package.

    Args:
        package_name: The package name as it appears in PyPI (e.g., "provide-foundation")
        caller_file: Path to the calling module's __file__, used to find VERSION file.
                    If None, uses the calling context.

    Returns:
        The current version string
    """
    global _cached_versions

    # Fast path: return cached version if available
    if package_name in _cached_versions:
        return _cached_versions[package_name]

    # Slow path: load version with thread-safe locking
    with _version_lock:
        # Double-check after acquiring lock
        if package_name in _cached_versions:
            return _cached_versions[package_name]

        # Determine start path for searching
        if caller_file is not None:
            start_path = Path(caller_file).parent
        else:
            # Try to infer from the call stack
            import inspect

            frame = inspect.currentframe()
            if frame and frame.f_back:
                caller_frame = frame.f_back
                start_path = Path(caller_frame.f_code.co_filename).parent
            else:
                start_path = Path.cwd()

        # Try VERSION file first (single source of truth)
        project_root = _find_project_root(start_path)
        if project_root:
            version_file = project_root / "VERSION"
            if version_file.exists():
                try:
                    version_str = version_file.read_text().strip()
                    _cached_versions[package_name] = version_str
                    return version_str
                except OSError:
                    # Fall back to metadata if VERSION file can't be read
                    pass

        # Fallback to package metadata
        try:
            from importlib.metadata import PackageNotFoundError, version as get_metadata_version

            version_str = get_metadata_version(package_name)
            _cached_versions[package_name] = version_str
            return version_str
        except PackageNotFoundError:
            pass

        # Final fallback
        version_str = "0.0.0-dev"
        _cached_versions[package_name] = version_str
        return version_str

reset_version_cache

reset_version_cache(
    package_name: str | None = None,
) -> None

Reset the cached version for testing.

Parameters:

Name Type Description Default
package_name str | None

Specific package to reset, or None to reset all

None
Warning

This should only be called from test code or test fixtures.

Source code in provide/foundation/utils/versioning.py
def reset_version_cache(package_name: str | None = None) -> None:
    """Reset the cached version for testing.

    Args:
        package_name: Specific package to reset, or None to reset all

    Warning:
        This should only be called from test code or test fixtures.
    """
    global _cached_versions
    with _version_lock:
        if package_name is None:
            _cached_versions.clear()
        else:
            _cached_versions.pop(package_name, None)