Add bzlmod support for python release

#test-continuous

PiperOrigin-RevId: 811607661
diff --git a/.github/workflows/test_upb.yml b/.github/workflows/test_upb.yml
index 187af00..2fb4174 100644
--- a/.github/workflows/test_upb.yml
+++ b/.github/workflows/test_upb.yml
@@ -138,7 +138,7 @@
           image: us-docker.pkg.dev/protobuf-build/release-containers/linux/apple:7.6.1-12e21b8dda91028bc14212a3ab582c7c4d149fac
           credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
           bazel-cache: upb-bazel-python
-          bazel: build --noenable_bzlmod --crosstool_top=//toolchain:clang_suite --//toolchain:release=true --symlink_prefix=/ -c dbg --incompatible_enable_cc_toolchain_resolution=false //python/dist //python/dist:test_wheel //python/dist:source_wheel
+          bazel: build --crosstool_top=//toolchain:clang_suite --//toolchain:release=true --symlink_prefix=/ -c dbg --incompatible_enable_cc_toolchain_resolution=false //python/dist //python/dist:test_wheel //python/dist:source_wheel
       - name: Move Wheels
         run: mkdir wheels && find _build/out \( -name 'protobuf*.whl' -o -name 'protobuf-*.tar.gz' \) -exec mv '{}' wheels ';'
       - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
diff --git a/MODULE.bazel b/MODULE.bazel
index 827c35a..712b4e6 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -303,3 +303,38 @@
     strip_prefix = "protobuf-29.0",
     urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v29.0/protobuf-29.0.tar.gz"],
 )
+
+# Python headers for release
+python_headers = use_extension("//python/dist:python_downloads.bzl", "python_headers", dev_dependency = True)
+python_headers.source_archive(
+    sha256 = "df796b2dc8ef085edae2597a41c1c0a63625ebd92487adaef2fed22b567873e8",
+    version = "3.9.0",
+)
+python_headers.nuget_package(
+    cpu = "i686",
+    sha256 = "229abecbe49dc08fe5709e0b31e70edfb3b88f23335ebfc2904c44f940fd59b6",
+    version = "3.9.0",
+)
+python_headers.nuget_package(
+    cpu = "x86-64",
+    sha256 = "6af58a733e7dfbfcdd50d55788134393d6ffe7ab8270effbf724bdb786558832",
+    version = "3.9.0",
+)
+python_headers.nuget_package(
+    cpu = "i686",
+    sha256 = "e115e102eb90ce160ab0ef7506b750a8d7ecc385bde0a496f02a54337a8bc333",
+    version = "3.10.0",
+)
+python_headers.nuget_package(
+    cpu = "x86-64",
+    sha256 = "4474c83c25625d93e772e926f95f4cd398a0abbb52793625fa30f39af3d2cc00",
+    version = "3.10.0",
+)
+use_repo(
+    python_headers,
+    "nuget_python_i686_3.10.0",
+    "nuget_python_i686_3.9.0",
+    "nuget_python_x86-64_3.10.0",
+    "nuget_python_x86-64_3.9.0",
+    "python-3.9.0",
+)
diff --git a/cmake/dependencies_generator.py b/cmake/dependencies_generator.py
index 22a9e53..6c32134 100644
--- a/cmake/dependencies_generator.py
+++ b/cmake/dependencies_generator.py
@@ -49,6 +49,12 @@
   def find(self, *args, **kwargs):
     pass
 
+  def source_archive(self, *args, **kwargs):
+    pass
+
+  def nuget_package(self, *args, **kwargs):
+    pass
+
 
 def empty_func(*args, **kwargs):
   pass
diff --git a/protobuf_deps.bzl b/protobuf_deps.bzl
index 2a2724c..d481463 100644
--- a/protobuf_deps.bzl
+++ b/protobuf_deps.bzl
@@ -204,23 +204,27 @@
 
     # Python Downloads
     python_source_archive(
-        name = "python-3.9.0",
+        version = "3.9.0",
         sha256 = "df796b2dc8ef085edae2597a41c1c0a63625ebd92487adaef2fed22b567873e8",
     )
     python_nuget_package(
-        name = "nuget_python_i686_3.9.0",
+        version = "3.9.0",
+        cpu = "i686",
         sha256 = "229abecbe49dc08fe5709e0b31e70edfb3b88f23335ebfc2904c44f940fd59b6",
     )
     python_nuget_package(
-        name = "nuget_python_x86-64_3.9.0",
+        version = "3.9.0",
+        cpu = "x86-64",
         sha256 = "6af58a733e7dfbfcdd50d55788134393d6ffe7ab8270effbf724bdb786558832",
     )
     python_nuget_package(
-        name = "nuget_python_i686_3.10.0",
+        version = "3.10.0",
+        cpu = "i686",
         sha256 = "e115e102eb90ce160ab0ef7506b750a8d7ecc385bde0a496f02a54337a8bc333",
     )
     python_nuget_package(
-        name = "nuget_python_x86-64_3.10.0",
+        version = "3.10.0",
+        cpu = "x86-64",
         sha256 = "4474c83c25625d93e772e926f95f4cd398a0abbb52793625fa30f39af3d2cc00",
     )
     native.register_toolchains("//bazel/private/toolchains:all")
diff --git a/python/dist/python_downloads.bzl b/python/dist/python_downloads.bzl
index 5ddac20..ef774f3 100644
--- a/python/dist/python_downloads.bzl
+++ b/python/dist/python_downloads.bzl
@@ -11,16 +11,15 @@
 )
 """
 
-def python_source_archive(name, sha256):
+def python_source_archive(version, sha256):
     """Helper method to create a python_headers target that will work for linux and macos.
 
     Args:
       name: The name of the target, should be in the form python_{VERSION}
       sha256: The sha256 of the python package for the specified version
     """
-    version = name.split("-")[1]
     http_archive(
-        name = name,
+        name = "python-{0}".format(version),
         urls = [
             "https://www.python.org/ftp/python/{0}/Python-{0}.tgz"
                 .format(version),
@@ -51,16 +50,13 @@
 )
 """
 
-def python_nuget_package(name, sha256):
+def python_nuget_package(cpu, version, sha256):
     """Helper method to create full and limited api dependencies for windows using nuget
 
     Args:
       name: The name of the target, should be in the form nuget_python_{CPU}_{VERSION}
       sha256: The sha256 of the nuget package for that version
     """
-    cpu = name.split("_")[2]
-    version = name.split("_")[3]
-
     full_api_lib_number = version.split(".")[0] + version.split(".")[1]
     limited_api_lib_number = version.split(".")[0]
 
@@ -70,7 +66,7 @@
     }
 
     http_archive(
-        name = name,
+        name = "nuget_python_{0}_{1}".format(cpu, version),
         urls = [
             "https://www.nuget.org/api/v2/package/{}/{}"
                 .format(folder_name_dict[cpu], version),
@@ -82,3 +78,36 @@
         type = "zip",
         patch_cmds = ["cp -r include/* ."],
     )
+
+def _python_headers(ctx):
+    for mod in ctx.modules:
+        for archive in mod.tags.source_archive:
+            python_source_archive(
+                version = archive.version,
+                sha256 = archive.sha256,
+            )
+        for pkg in mod.tags.nuget_package:
+            python_nuget_package(
+                version = pkg.version,
+                cpu = pkg.cpu,
+                sha256 = pkg.sha256,
+            )
+
+source_archive = tag_class(attrs = {
+    "version": attr.string(doc = "A python source archive"),
+    "sha256": attr.string(),
+})
+
+nuget_package = tag_class(attrs = {
+    "version": attr.string(doc = "A python nuget package"),
+    "cpu": attr.string(),
+    "sha256": attr.string(),
+})
+
+python_headers = module_extension(
+    implementation = _python_headers,
+    tag_classes = {
+        "source_archive": source_archive,
+        "nuget_package": nuget_package,
+    },
+)