diff --git a/README.md b/README.md
index 77d0df752..e571cb557 100644
--- a/README.md
+++ b/README.md
@@ -15,18 +15,12 @@
-
-
-
-
-
-
@@ -255,7 +249,7 @@ Apache Hamilton was started at Stitch Fix before the original creators founded D
* [wren.ai](https://wren.ai/)
## 🤝 Code Contributors
-[](https://github.com/apache/hamilton/graphs/contributors)
+[](https://github.com/apache/hamilton/graphs/contributors)
## 🙌 Special Mentions & 🦟 Bug Hunters
diff --git a/docs/asf/downloads.rst b/docs/asf/downloads.rst
new file mode 100644
index 000000000..124f29db1
--- /dev/null
+++ b/docs/asf/downloads.rst
@@ -0,0 +1,57 @@
+================
+Downloads
+================
+
+Official Apache Hamilton releases are available at the Apache Software Foundation distribution site.
+
+Release Downloads
+-----------------
+
+All releases can be found at: `https://downloads.apache.org/incubator/hamilton/ `_
+
+Each release includes:
+
+- Source distributions (.tar.gz, .zip)
+- Checksums (SHA512)
+- Digital signatures (.asc)
+
+Verifying Releases
+------------------
+
+To verify the integrity of downloaded files, you can:
+
+1. Verify the checksum matches the published SHA512 sum
+2. Verify the GPG signature using the KEYS file available at the downloads site
+
+Release History
+---------------
+
+Visit the `downloads directory `_ to see all available releases.
+
+Installation
+------------
+
+After downloading and verifying a release, you can install it using pip:
+
+.. code-block:: bash
+
+ pip install apache-hamilton-.tar.gz
+
+Or install directly from PyPI:
+
+.. code-block:: bash
+
+ pip install apache-hamilton
+
+For more information about installation options, see the :doc:`Get Started guide `.
+
+Official Releases
+-----------------
+
+The following are the official Apache Hamilton releases:
+
+- **1.89.0** (2025-10-11) - First Apache Hamilton release
+
+ - `Source (tar.gz) `_
+ - `Checksums `_
+ - `Signature `_
diff --git a/docs/asf/index.rst b/docs/asf/index.rst
index 88e9ef25b..b810e6505 100644
--- a/docs/asf/index.rst
+++ b/docs/asf/index.rst
@@ -10,6 +10,7 @@ Apache Software Foundation links.
:glob:
:hidden:
+ downloads
Apache Software Foundation
License
Events
@@ -19,6 +20,7 @@ Apache Software Foundation links.
Thanks
Code of Conduct
+- :doc:`Downloads `
- `Foundation `_
- `License `_
- `Events `_
diff --git a/docs/index.md b/docs/index.md
index b88d3b610..64559ce1c 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -15,6 +15,13 @@ integrations/index
code-comparisons/index
```
+```{toctree}
+:hidden: True
+:caption: DOWNLOADS
+
+asf/downloads
+```
+
```{toctree}
:hidden: True
:caption: PDF
diff --git a/pyproject.toml b/pyproject.toml
index 2e58e897e..d8a1e73e9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,11 +20,27 @@ requires = ["flit_core >=3.11,<4"]
build-backend = "flit_core.buildapi"
[project]
-name = "sf-hamilton"
+name = "apache-hamilton"
version = "1.89.0" # NOTE: keep this in sync with hamilton/version.py
# TODO: flip back to dynamic once hamilton version is a string. Flit doesn't handle tuples.
# dynamic = ["version"]
-description = "Hamilton, the micro-framework for creating dataframes."
+description = """Apache Hamilton (incubating) is a lightweight Python library for directed acyclic graphs (DAGs)
+of transformations. Your DAG is **portable**; it runs anywhere Python runs, whether it's a script,
+notebook, Airflow pipeline, FastAPI server, etc. Your DAG is **expressive**; Apache Hamilton has extensive
+features to define and modify the execution of a DAG (e.g., data validation, experiment tracking, remote
+execution).
+
+Apache Hamilton (incubating) is an effort undergoing incubation at the Apache
+Software Foundation (ASF), sponsored by the Apache Incubator PMC.
+
+Incubation is required of all newly accepted projects until a further review
+indicates that the infrastructure, communications, and decision making process
+have stabilized in a manner consistent with other successful ASF projects.
+
+While incubation status is not necessarily a reflection of the completeness
+or stability of the code, it does indicate that the project has yet to be
+fully endorsed by the ASF.
+"""
readme = "README.md"
requires-python = ">=3.8.1, <4"
license = {text = "Apache-2.0"}
diff --git a/scripts/apache_release_helper.py b/scripts/apache_release_helper.py
index 9d40f6b31..4170f464f 100644
--- a/scripts/apache_release_helper.py
+++ b/scripts/apache_release_helper.py
@@ -22,6 +22,9 @@
import shutil
import subprocess
import sys
+import tarfile
+import tempfile
+import zipfile
from typing import Optional
# --- Configuration ---
@@ -139,10 +142,96 @@ def sign_artifacts(archive_name: str) -> Optional[list[str]]:
return files
+def _modify_wheel_for_apache_release(original_wheel: str, new_wheel_path: str):
+ """Helper to modify the wheel for apache release.
+
+ # Flit somehow builds something incorrectly.
+ # 1. change PKG-INFO's first line to be `Metadata-Version: 2.4`
+ # 2. make sure the second line is `Name: apache-hamilton`
+ # 3. remove the `Import-Name: hamilton` line from PKG-INFO.
+
+ :param original_wheel: Path to the original wheel.
+ :param new_wheel_path: Path to the new wheel to create.
+ """
+ with tempfile.TemporaryDirectory() as tmpdir:
+ # Unzip the wheel
+ with zipfile.ZipFile(original_wheel, "r") as zip_ref:
+ zip_ref.extractall(tmpdir)
+
+ # Find the .dist-info directory
+ dist_info_dirs = glob.glob(os.path.join(tmpdir, "*.dist-info"))
+ if not dist_info_dirs:
+ raise ValueError(f"Could not find .dist-info directory in {original_wheel}")
+ dist_info_dir = dist_info_dirs[0]
+ pkg_info = os.path.join(dist_info_dir, "PKG-INFO")
+
+ _modify_pkg_info_file(pkg_info)
+
+ # Create the new wheel
+ with zipfile.ZipFile(new_wheel_path, "w", zipfile.ZIP_DEFLATED) as zip_ref:
+ for root, _, files in os.walk(tmpdir):
+ for file in files:
+ zip_ref.write(
+ os.path.join(root, file), os.path.relpath(os.path.join(root, file), tmpdir)
+ )
+
+
+def _modify_pkg_info_file(pkg_info_path: str):
+ """
+ Flit somehow builds something incorrectly.
+ 1. change PKG-INFO's first line to be `Metadata-Version: 2.4`
+ 2. make sure the second line is `Name: apache-hamilton`
+ 3. remove the `Import-Name: hamilton` line from PKG-INFO.
+ """
+ with open(pkg_info_path, "r") as f:
+ lines = f.readlines()
+
+ new_lines = []
+ for i, line in enumerate(lines):
+ if i == 0:
+ new_lines.append("Metadata-Version: 2.4\n")
+ elif i == 1:
+ new_lines.append("Name: apache-hamilton\n")
+ elif line.strip() == "Import-Name: hamilton":
+ continue # Skip this line
+ else:
+ new_lines.append(line)
+
+ with open(pkg_info_path, "w") as f:
+ f.writelines(new_lines)
+
+
+def _modify_tarball_for_apache_release(original_tarball: str, new_tarball_path: str):
+ """Helper to modify the tarball for apache release.
+
+ # Flit somehow builds something incorrectly.
+ # 1. change PKG-INFO's first line to be `Metadata-Version: 2.4`
+ # 2. make sure the second line is `Name: apache-hamilton`
+ # 3. remove the `Import-Name: hamilton` line from PKG-INFO.
+
+ :param original_tarball: Path to the original tarball.
+ :param new_tarball_path: Path to the new tarball to create.
+ """
+ with tempfile.TemporaryDirectory() as tmpdir:
+ # Extract the tarball
+ with tarfile.open(original_tarball, "r:gz") as tar:
+ tar.extractall(path=tmpdir)
+
+ # Modify the PKG-INFO file
+ # The extracted tarball has a single directory inside.
+ extracted_dir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+ pkg_info_path = os.path.join(extracted_dir, "PKG-INFO")
+
+ _modify_pkg_info_file(pkg_info_path)
+
+ # Create the new tarball
+ with tarfile.open(new_tarball_path, "w:gz") as tar:
+ tar.add(extracted_dir, arcname=os.path.basename(extracted_dir))
+
+
def create_release_artifacts(version) -> list[str]:
"""Creates the source tarball, GPG signature, and checksums using `python -m build`."""
- print("Creating release artifacts with 'python -m build'...")
- files_to_upload = []
+ print("Creating release artifacts with 'flit build'...")
# Clean the dist directory before building.
if os.path.exists("dist"):
shutil.rmtree("dist")
@@ -162,8 +251,7 @@ def create_release_artifacts(version) -> list[str]:
return None
# Find the created tarball in the dist directory.
- expected_tar_ball = f"dist/sf_hamilton-{version.lower()}.tar.gz"
- files_to_upload.append(expected_tar_ball)
+ expected_tar_ball = f"dist/apache-hamilton-{version.lower()}.tar.gz"
tarball_path = glob.glob(expected_tar_ball)
if not tarball_path:
@@ -180,29 +268,22 @@ def create_release_artifacts(version) -> list[str]:
# copy the tarball to be apache-hamilton-{version.lower()}-incubating.tar.gz
new_tar_ball = f"dist/apache-hamilton-{version.lower()}-incubating.tar.gz"
- shutil.copy(tarball_path[0], new_tar_ball)
+ _modify_tarball_for_apache_release(tarball_path[0], new_tar_ball)
archive_name = new_tar_ball
print(f"Found source tarball: {archive_name}")
- main_signed_files = sign_artifacts(archive_name)
- if main_signed_files is None:
+ new_tar_ball_singed = sign_artifacts(archive_name)
+ if new_tar_ball_singed is None:
raise ValueError("Could not sign the main release artifacts.")
- # create sf-hamilton release artifacts
- sf_hamilton_signed_files = sign_artifacts(expected_tar_ball)
# create wheel release artifacts
- expected_wheel = f"dist/sf_hamilton-{version.lower()}-py3-none-any.whl"
+ expected_wheel = f"dist/apache-hamilton-{version.lower()}-py3-none-any.whl"
wheel_path = glob.glob(expected_wheel)
- wheel_signed_files = sign_artifacts(wheel_path[0])
# create incubator wheel release artifacts
expected_incubator_wheel = f"dist/apache-hamilton-{version.lower()}-incubating-py3-none-any.whl"
shutil.copy(wheel_path[0], expected_incubator_wheel)
incubator_wheel_signed_files = sign_artifacts(expected_incubator_wheel)
files_to_upload = (
[new_tar_ball]
- + main_signed_files
- + [expected_tar_ball]
- + sf_hamilton_signed_files
- + [expected_wheel]
- + wheel_signed_files
+ + new_tar_ball_singed
+ [expected_incubator_wheel]
+ incubator_wheel_signed_files
)
diff --git a/scripts/promote_rc.sh b/scripts/promote_rc.sh
new file mode 100644
index 000000000..fac10592a
--- /dev/null
+++ b/scripts/promote_rc.sh
@@ -0,0 +1,107 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# This script moves a release candidate from the dev repository to the release repository.
+
+set -e
+
+DRY_RUN=false
+POSITIONAL_ARGS=()
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --dry-run)
+ DRY_RUN=true
+ shift # past argument
+ ;;
+ *)
+ POSITIONAL_ARGS+=("$1") # save positional arg
+ shift # past argument
+ ;;
+ esac
+done
+
+set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
+
+if [ "$#" -ne 2 ]; then
+ echo "Usage: $0 [--dry-run] "
+ echo "Example: $0 --dry-run 1.2.3 0"
+ exit 1
+fi
+
+VERSION=$1
+RC_NUM=$2
+PROJECT_SHORT_NAME="hamilton"
+
+# Source and destination URLs
+SOURCE_URL="https://dist.apache.org/repos/dist/dev/incubator/${PROJECT_SHORT_NAME}/${VERSION}-RC${RC_NUM}"
+DEST_URL="https://dist.apache.org/repos/dist/release/incubator/${PROJECT_SHORT_NAME}/${VERSION}"
+
+if [ "$DRY_RUN" = true ]; then
+ echo "Performing a dry run. No changes will be made."
+else
+ echo "This script will copy the release candidate from dev to release."
+ echo "Source: ${SOURCE_URL}"
+ echo "Destination: ${DEST_URL}"
+ echo " "
+
+ read -p "Are you sure you want to continue? (y/n) " -n 1 -r
+ echo
+ if [[ ! $REPLY =~ ^[Yy]$ ]]
+ then
+ echo "Aborting."
+ exit 1
+ fi
+fi
+
+# Create the release directory
+if [ "$DRY_RUN" = true ]; then
+ echo "DRY RUN: svn mkdir --parents -m \"Creating directory for Apache ${PROJECT_SHORT_NAME} ${VERSION}\" \"${DEST_URL}\""
+else
+ svn mkdir --parents -m "Creating directory for Apache ${PROJECT_SHORT_NAME} ${VERSION}" "${DEST_URL}"
+fi
+
+# Get the list of files in the source directory
+FILES=$(svn list "${SOURCE_URL}")
+
+for FILE in $FILES; do
+ if [[ "$FILE" == apache-hamilton* ]]; then
+ SOURCE_FILE_URL="${SOURCE_URL}/${FILE}"
+ DEST_FILE_URL="${DEST_URL}/${DEST_FILE_NAME}"
+
+ if [ "$DRY_RUN" = true ]; then
+ echo "DRY RUN: svn cp \"${SOURCE_FILE_URL}\" \"${DEST_FILE_URL}\" -m \"Promote Apache ${PROJECT_SHORT_NAME} ${VERSION}: ${DEST_FILE_NAME}\""
+ else
+ echo "Copying ${FILE} to ${DEST_FILE_URL}"
+ svn cp "${SOURCE_FILE_URL}" "${DEST_FILE_URL}" -m "Promote Apache ${PROJECT_SHORT_NAME} ${VERSION}: ${DEST_FILE_NAME}"
+ fi
+ fi
+done
+
+
+if [ $? -eq 0 ]; then
+ if [ "$DRY_RUN" = true ]; then
+ echo "Dry run complete."
+ else
+ echo "Successfully copied release artifacts to: ${DEST_URL}"
+ echo "The release is now live."
+ fi
+else
+ echo "Error: Failed to copy release artifacts. Please check the SVN URLs and your credentials."
+ exit 1
+fi