From 93c000c3fe1d737a06452abb5cc1983094425f27 Mon Sep 17 00:00:00 2001 From: Fabien Hertschuh <1091026+hertschuh@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:36:09 -0700 Subject: [PATCH] Add `pip_build.py` and required `__init__.py` files, update `README.md`. --- README.md | 24 +++- keras_rs/src/__init__.py | 0 keras_rs/src/layers/__init__.py | 0 keras_rs/src/layers/retrieval/__init__.py | 0 pip_build.py | 140 ++++++++++++++++++++++ 5 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 keras_rs/src/__init__.py create mode 100644 keras_rs/src/layers/__init__.py create mode 100644 keras_rs/src/layers/retrieval/__init__.py create mode 100644 pip_build.py diff --git a/README.md b/README.md index 4b70ac8..39160a8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,28 @@ This library is an extension of the core Keras API; all high-level modules receive that same level of polish as core Keras. If you are familiar with Keras, congratulations! You already understand most of Keras Recommenders. +## Installation + +Keras Recommenders is available on PyPI as `keras-rs`: + +```bash +pip install keras-rs +``` + +To try out the latest version of Keras Recommenders, you can use our nightly +package: + +```bash +pip install keras-rs-nightly +``` + +Read [Getting started with Keras](https://keras.io/getting_started/) for more +information on installing Keras 3 and compatibility with different frameworks. + +> [!IMPORTANT] +> We recommend using Keras Recommenders with TensorFlow 2.16 or later, as +> TF 2.16 packages Keras 3 by default. + ## Configuring your backend If you have Keras 3 installed in your environment (see installation above), you @@ -27,7 +49,7 @@ Or in Colab, with: import os os.environ["KERAS_BACKEND"] = "jax" -import keras_recommenders +import keras_rs ``` > [!IMPORTANT] diff --git a/keras_rs/src/__init__.py b/keras_rs/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/keras_rs/src/layers/__init__.py b/keras_rs/src/layers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/keras_rs/src/layers/retrieval/__init__.py b/keras_rs/src/layers/retrieval/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pip_build.py b/pip_build.py new file mode 100644 index 0000000..24320fc --- /dev/null +++ b/pip_build.py @@ -0,0 +1,140 @@ +"""Script to create (and optionally install) a `.whl` archive for Keras RS. + +Usage: + +1. Create a `.whl` file in `dist/`: + +``` +python3 pip_build.py +``` + +2. Also install the new package immediately after: + +``` +python3 pip_build.py --install +``` +""" + +import argparse +import datetime +import os +import pathlib +import re +import shutil + +package = "keras_rs" +build_directory = "tmp_build_dir" +dist_directory = "dist" +to_copy = ["setup.py", "README.md"] + + +def ignore_files(path: str, filenames: list[str]) -> list[str]: + if path.endswith("testing"): + return filenames + return [f for f in filenames if f.endswith("_test.py")] + + +def update_version( + build_path: pathlib.Path, version: str, is_nightly: bool +) -> None: + """Export Version and Package Name.""" + if is_nightly: + date = datetime.datetime.now() + version += f".dev{date.strftime('%Y%m%d%H')}" + # Replaces `name="keras-rs"` in `setup.py` with `keras-rs-nightly` + with open(build_path / "setup.py") as f: + setup_contents = f.read() + with open(build_path / "setup.py", "w") as f: + setup_contents = setup_contents.replace( + 'name="keras-rs"', 'name="keras-rs-nightly"' + ) + f.write(setup_contents) + + # Make sure to export the __version__ string + version_utils = build_path / package / "src" / "version_utils.py" + with open(version_utils) as f: + contents = f.read() + with open(version_utils, "w") as f: + contents = re.sub( + "\n__version__ = .*\n", + f'\n__version__ = "{version}"\n', + contents, + ) + f.write(contents) + + +def copy_source_to_build_directory(root_path: pathlib.Path) -> None: + # Copy sources (`keras_rs/` directory and setup files) to build directory + shutil.copytree( + root_path / package, + root_path / build_directory / package, + ignore=ignore_files, + ) + for fname in to_copy: + shutil.copy(root_path / fname, root_path / build_directory / fname) + + +def build_wheel( + build_path: pathlib.Path, dist_path: pathlib.Path, __version__: str +) -> pathlib.Path: + # Build the package + os.chdir(build_path) + os.system("python3 -m build") + + # Save the dist files generated by the build process + if not os.path.exists(dist_path): + os.mkdir(dist_path) + for fpath in (build_path / dist_directory).glob("*.*"): + shutil.copy(fpath, dist_path) + + # Find the .whl file path + for fname in os.listdir(dist_path): + if __version__ in fname and fname.endswith(".whl"): + whl_path = dist_path / fname + print(f"Build successful. Wheel file available at {whl_path}") + return whl_path + raise FileNotFoundError("Build failed") + + +def build(root_path: pathlib.Path, is_nightly: bool) -> pathlib.Path: + if os.path.exists(build_directory): + raise ValueError(f"Directory already exists: {build_directory}") + + try: + build_path = root_path / build_directory + dist_path = root_path / dist_directory + os.mkdir(build_path) + + from keras_rs.src.version_utils import __version__ # noqa: E402 + + copy_source_to_build_directory(root_path) + update_version(build_path, __version__, is_nightly) + return build_wheel(build_path, dist_path, __version__) + finally: + # Clean up: remove the build directory (no longer needed) + os.chdir(root_path) + shutil.rmtree(root_path / build_directory) + + +def install_whl(whl_fpath: pathlib.Path) -> None: + print(f"Installing wheel file: {whl_fpath}") + os.system(f"pip3 install {whl_fpath} --force-reinstall --no-dependencies") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--install", + action="store_true", + help="Whether to install the generated wheel file.", + ) + parser.add_argument( + "--nightly", + action="store_true", + help="Whether to generate nightly wheel file.", + ) + args = parser.parse_args() + root_path = pathlib.Path(__file__).parent.resolve() + whl_path = build(root_path, args.nightly) + if whl_path and args.install: + install_whl(whl_path)