feat(pypi): builder for netrc and auth_patterns (#3136)

With this we move closer towards starting playing with the API to fully
replace `pip.parse` with `pip.configure` builder pattern for better
expressiveness.

Work towards #2747
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d45aa5..0e8ad65 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -128,7 +128,8 @@
   ([#3114](https://github.com/bazel-contrib/rules_python/pull/3114)).
 * (pypi) To configure the environment for `requirements.txt` evaluation, use the newly added
   developer preview of the `pip.default` tag class. Only `rules_python` and root modules can use
-  this feature. You can also configure custom `config_settings` using `pip.default`.
+  this feature. You can also configure custom `config_settings` using `pip.default`. It
+  can also be used to set the global `netrc` or `auth_patterns` variables.
 * (pypi) PyPI dependencies now expose an `:extracted_whl_files` filegroup target
   of all the files extracted from the wheel. This can be used in lieu of
   {obj}`whl_filegroup` to avoid copying/extracting wheel multiple times to
diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl
index 2c7aa8a..0c06dea 100644
--- a/python/private/pypi/extension.bzl
+++ b/python/private/pypi/extension.bzl
@@ -322,12 +322,12 @@
                 src = src,
                 whl_library_args = whl_library_args,
                 download_only = pip_attr.download_only,
-                netrc = pip_attr.netrc,
+                netrc = config.netrc or pip_attr.netrc,
                 use_downloader = use_downloader.get(
                     whl.name,
                     get_index_urls != None,  # defaults to True if the get_index_urls is defined
                 ),
-                auth_patterns = pip_attr.auth_patterns,
+                auth_patterns = config.auth_patterns or pip_attr.auth_patterns,
                 python_version = major_minor,
                 is_multiple_versions = whl.is_multiple_versions,
                 enable_pipstar = config.enable_pipstar,
@@ -502,13 +502,20 @@
                 if platform and not (tag.arch_name or tag.config_settings or tag.env or tag.os_name or tag.whl_abi_tags or tag.whl_platform_tags):
                     defaults["platforms"].pop(platform)
 
-            # TODO @aignas 2025-05-19: add more attr groups:
-            # * for AUTH - the default `netrc` usage could be configured through a common
-            # attribute.
-            # * for index/downloader config. This includes all of those attributes for
-            # overrides, etc. Index overrides per platform could be also used here.
+            _configure(
+                defaults,
+                override = mod.is_root,
+                # extra values that we just add
+                auth_patterns = tag.auth_patterns,
+                netrc = tag.netrc,
+                # TODO @aignas 2025-05-19: add more attr groups:
+                # * for index/downloader config. This includes all of those attributes for
+                # overrides, etc. Index overrides per platform could be also used here.
+            )
 
     return struct(
+        auth_patterns = defaults.get("auth_patterns", {}),
+        netrc = defaults.get("netrc", None),
         platforms = {
             name: _plat(**values)
             for name, values in defaults["platforms"].items()
@@ -975,7 +982,7 @@
 :::
 """,
     ),
-}
+} | AUTH_ATTRS
 
 _SUPPORTED_PEP508_KEYS = [
     "implementation_name",
diff --git a/tests/pypi/extension/extension_tests.bzl b/tests/pypi/extension/extension_tests.bzl
index 72cbb61..ab8362e 100644
--- a/tests/pypi/extension/extension_tests.bzl
+++ b/tests/pypi/extension/extension_tests.bzl
@@ -100,28 +100,34 @@
             **kwargs
         ),
         attrs = dict(
-            platforms = subjects.dict,
+            auth_patterns = subjects.dict,
             enable_pipstar = subjects.bool,
+            netrc = subjects.str,
+            platforms = subjects.dict,
         ),
     )
 
 def _default(
         *,
         arch_name = None,
+        auth_patterns = None,
         config_settings = None,
+        env = None,
+        netrc = None,
         os_name = None,
         platform = None,
         whl_platform_tags = None,
-        env = None,
         whl_abi_tags = None):
     return struct(
         arch_name = arch_name,
-        os_name = os_name,
-        platform = platform,
-        whl_platform_tags = whl_platform_tags or [],
+        auth_patterns = auth_patterns or {},
         config_settings = config_settings,
         env = env or {},
+        netrc = netrc,
+        os_name = os_name,
+        platform = platform,
         whl_abi_tags = whl_abi_tags or [],
+        whl_platform_tags = whl_platform_tags or [],
     )
 
 def _parse(
@@ -1224,11 +1230,17 @@
                         ],
                     ),
                     _default(platform = "myplat2"),
+                    _default(
+                        netrc = "my_netrc",
+                        auth_patterns = {"foo": "bar"},
+                    ),
                 ],
             ),
         ),
         enable_pipstar = True,
     )
+    config.auth_patterns().contains_exactly({"foo": "bar"})
+    config.netrc().equals("my_netrc")
     config.enable_pipstar().equals(True)
     config.platforms().contains_exactly({
         "myplat": struct(