diff --git a/docs/installation.md b/docs/installation.md index 7146ec0f3b..d42cca2bc5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -27,10 +27,10 @@ To verify the installation, start the Python REPL and execute: -```python ->>> import narwhals ->>> narwhals.__version__ -'1.38.2' +```python exec="1" source="above" session="quickstart" result="python" +import narwhals + +print(narwhals.__version__) ``` If you see the version number, then the installation was successful! diff --git a/narwhals/__init__.py b/narwhals/__init__.py index 3c11123161..b4008d460b 100644 --- a/narwhals/__init__.py +++ b/narwhals/__init__.py @@ -1,5 +1,7 @@ from __future__ import annotations +import typing as _t + from narwhals import dependencies from narwhals import dtypes from narwhals import exceptions @@ -81,7 +83,7 @@ from narwhals.utils import maybe_reset_index from narwhals.utils import maybe_set_index -__version__ = "1.38.2" +__version__: str __all__ = [ "Array", @@ -165,3 +167,16 @@ "to_py_scalar", "when", ] + + +def __getattr__(name: _t.Literal["__version__"]) -> str: # type: ignore[misc] + if name == "__version__": + global __version__ # noqa: PLW0603 + + from importlib import metadata + + __version__ = metadata.version(__name__) + return __version__ + else: + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) diff --git a/pyproject.toml b/pyproject.toml index 79a35cae82..02859e244a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,7 +124,7 @@ exclude = [ line-length = 90 fix = true target-version = "py38" -extend-exclude = ["utils/bump_version.py", "**/this.py"] +extend-exclude = ["**/this.py"] [tool.ruff.lint] preview = true @@ -189,6 +189,7 @@ builtins-ignorelist = ["format"] ] "tpch/tests/*" = ["S101"] "utils/*" = ["S311", "PTH123"] +"utils/bump_version.py" = ["S603", "S607", "T201"] "tpch/execute/*" = ["T201"] "tpch/notebooks/*" = [ "ANN001", diff --git a/tests/version_test.py b/tests/version_test.py new file mode 100644 index 0000000000..1a1cb034e2 --- /dev/null +++ b/tests/version_test.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +import re +from pathlib import Path + +import narwhals as nw + + +def test_version_matches_pyproject() -> None: + with Path("pyproject.toml").open(encoding="utf-8") as file: + content = file.read() + pyproject_version = re.search(r'version = "(.*)"', content).group(1) # type: ignore[union-attr] + + assert nw.__version__ == pyproject_version diff --git a/utils/bump_version.py b/utils/bump_version.py index fe4079d607..24dd786ab0 100644 --- a/utils/bump_version.py +++ b/utils/bump_version.py @@ -1,80 +1,61 @@ # mypy: ignore -# ruff: noqa -import re -import subprocess +from __future__ import annotations + +import subprocess as sp import sys -out = subprocess.run(["git", "fetch", "upstream", "--tags"]) +GIT = "git" +UV = "uv" +FETCH = "fetch" +PUSH = "push" +COMMIT = "commit" +UPSTREAM = "upstream" +TAGS = "--tags" +TAG = "tag" +VERSION = "version" + +out = sp.run([GIT, FETCH, UPSTREAM, TAGS], check=False) if out.returncode != 0: print( - "Something went wrong with the release process, please check the Narwhals Wiki and try again." + "Something went wrong with the release process, please check the Narwhals Wiki for " + "at https://github.com/narwhals-dev/narwhals/wiki#release-process and try again." ) print(out) sys.exit(1) -subprocess.run(["git", "reset", "--hard", "upstream/main"]) +sp.run([GIT, "reset", "--hard", "upstream/main"], check=False) if ( - subprocess.run( - ["git", "branch", "--show-current"], text=True, capture_output=True + current_branch := sp.run( + [GIT, "branch", "--show-current"], text=True, capture_output=True, check=False ).stdout.strip() - != "bump-version" -): - msg = "`bump_version.py` should be run from `bump-version` branch" +) != "bump-version": + msg = f"`bump_version.py` should be run from `bump-version` branch instead of `{current_branch}`" raise RuntimeError(msg) # Delete local tags, if present try: # Get the list of all tags - result = subprocess.run( - ["git", "tag", "-l"], capture_output=True, text=True, check=True - ) + result = sp.run([GIT, TAG, "-l"], capture_output=True, text=True, check=True) tags = result.stdout.splitlines() # Split the tags into a list by lines - # Delete each tag using git tag -d - for tag in tags: - subprocess.run(["git", "tag", "-d", tag], check=True) + if tags: + # Delete each tag using git tag -d + sp.run([GIT, TAG, "-d", *tags], check=True) + print("All local tags have been deleted.") -except subprocess.CalledProcessError as e: +except sp.CalledProcessError as e: print(f"An error occurred: {e}") -subprocess.run(["git", "fetch", "upstream", "--tags"]) -subprocess.run(["git", "fetch", "upstream", "--prune", "--tags"]) +sp.run([GIT, FETCH, UPSTREAM, TAGS], check=False) +sp.run([GIT, FETCH, UPSTREAM, "--prune", TAGS], check=False) how = sys.argv[1] -with open("pyproject.toml", encoding="utf-8") as f: - content = f.read() -old_version = re.search(r'version = "(.*)"', content).group(1) # pyright: ignore[reportOptionalMemberAccess] -version = old_version.split(".") -if how == "patch": - version = ".".join(version[:-1] + [str(int(version[-1]) + 1)]) -elif how == "minor": - version = ".".join(version[:-2] + [str(int(version[-2]) + 1), "0"]) -elif how == "major": - version = ".".join([str(int(version[0]) + 1), "0", "0"]) -content = content.replace(f'version = "{old_version}"', f'version = "{version}"') -with open("pyproject.toml", "w", encoding="utf-8") as f: - f.write(content) - -with open("narwhals/__init__.py", encoding="utf-8") as f: - content = f.read() -content = content.replace( - f'__version__ = "{old_version}"', - f'__version__ = "{version}"', -) -with open("narwhals/__init__.py", "w", encoding="utf-8") as f: - f.write(content) - -with open("docs/installation.md", encoding="utf-8") as f: - content = f.read() -content = content.replace( - f"'{old_version}'", - f"'{version}'", -) -with open("docs/installation.md", "w", encoding="utf-8") as f: - f.write(content) +new_version = sp.run( + [UV, VERSION, "--bump", how, "--short"], capture_output=True, text=True, check=False +).stdout -subprocess.run(["git", "commit", "-a", "-m", f"release: Bump version to {version}"]) -subprocess.run(["git", "tag", "-a", f"v{version}", "-m", f"v{version}"]) -subprocess.run(["git", "push", "upstream", "HEAD", "--follow-tags"]) -subprocess.run(["git", "push", "upstream", "HEAD:stable", "-f", "--follow-tags"]) +sp.run([GIT, COMMIT, "-a", "-m", f"release: Bump version to {new_version}"], check=False) +sp.run([GIT, TAG, "-a", f"v{new_version}", "-m", f"v{new_version}"], check=False) +sp.run([GIT, PUSH, UPSTREAM, "HEAD", "--follow-tags"], check=False) +sp.run([GIT, PUSH, UPSTREAM, "HEAD:stable", "-f", "--follow-tags"], check=False) diff --git a/utils/check_api_reference.py b/utils/check_api_reference.py index 5954abfd6b..803d6d4e69 100644 --- a/utils/check_api_reference.py +++ b/utils/check_api_reference.py @@ -76,7 +76,7 @@ def iter_api_reference_names(tp: type[Any]) -> Iterator[str]: # Top level functions top_level_functions = [ - i for i in dir(nw) if not i[0].isupper() and i[0] != "_" and i not in files + i for i in nw.__all__ if not i[0].isupper() and i[0] != "_" and i not in files ] with open("docs/api-reference/narwhals.md") as fd: content = fd.read()