Repository: rasterio/rasterio

Issue:
Add missing support for unsigned and requester-pays cloud requests
Most of the work will be in env, but the main rio command will need options like `--no-sign-request` and `--requester-pays`. The AWS CLI has a `--request-payer requester` option, but that seems too flexible. The client can either agree to pay or not and that's it, right?


Discussion:
>  The client can either agree to pay or not and that's it, right?

well if the client doesn't want to pay then it won't have access to the bucket content. 

That's say I'm not sure if the `--request-payer`option  will make you pay for request on non requester-pays bucket 🤔 
Right, I'm saying that a `--request-payer=requester` with only one possible value is better implemented as `--requester-pays`.

Resolving patch:
diff --git a/rasterio/rio/main.py b/rasterio/rio/main.py
--- a/rasterio/rio/main.py
+++ b/rasterio/rio/main.py
@@ -41,6 +41,7 @@
 
 from . import options
 import rasterio
+from rasterio.session import AWSSession
 
 
 def configure_logging(verbosity):
@@ -51,28 +52,50 @@ def configure_logging(verbosity):
 def gdal_version_cb(ctx, param, value):
     if not value or ctx.resilient_parsing:
         return
+
     click.echo("{0}".format(rasterio.__gdal_version__), color=ctx.color)
     ctx.exit()
 
 
-@with_plugins(ep for ep in list(iter_entry_points('rasterio.rio_commands')) +
-              list(iter_entry_points('rasterio.rio_plugins')))
+@with_plugins(
+    ep
+    for ep in list(iter_entry_points("rasterio.rio_commands"))
+    + list(iter_entry_points("rasterio.rio_plugins"))
+)
 @click.group()
 @cligj.verbose_opt
 @cligj.quiet_opt
-@click.option('--aws-profile',
-              help="Selects a profile from your shared AWS credentials file")
-@click.version_option(version=rasterio.__version__, message='%(version)s')
-@click.option('--gdal-version', is_eager=True, is_flag=True,
-              callback=gdal_version_cb)
+@click.option(
+    "--aws-profile", help="Select a profile from the AWS credentials file"
+)
+@click.option("--aws-no-sign-requests", is_flag=True, help="Make requests anonymously")
+@click.option(
+    "--aws-requester-pays", is_flag=True, help="Requester pays data transfer costs"
+)
+@click.version_option(version=rasterio.__version__, message="%(version)s")
+@click.option("--gdal-version", is_eager=True, is_flag=True, callback=gdal_version_cb)
 @click.pass_context
-def main_group(ctx, verbose, quiet, aws_profile, gdal_version):
+def main_group(
+    ctx,
+    verbose,
+    quiet,
+    aws_profile,
+    aws_no_sign_requests,
+    aws_requester_pays,
+    gdal_version,
+):
     """Rasterio command line interface.
     """
     verbosity = verbose - quiet
     configure_logging(verbosity)
     ctx.obj = {}
-    ctx.obj['verbosity'] = verbosity
-    ctx.obj['aws_profile'] = aws_profile
-    ctx.obj['env'] = rasterio.Env(CPL_DEBUG=(verbosity > 2),
-                                  profile_name=aws_profile)
+    ctx.obj["verbosity"] = verbosity
+    ctx.obj["aws_profile"] = aws_profile
+    ctx.obj["env"] = rasterio.Env(
+        session=AWSSession(
+            profile_name=aws_profile,
+            aws_unsigned=aws_no_sign_requests,
+            requester_pays=aws_requester_pays,
+        ),
+        CPL_DEBUG=(verbosity > 2)
+    )
diff --git a/rasterio/session.py b/rasterio/session.py
--- a/rasterio/session.py
+++ b/rasterio/session.py
@@ -118,7 +118,7 @@ class AWSSession(Session):
     def __init__(
             self, session=None, aws_unsigned=False, aws_access_key_id=None,
             aws_secret_access_key=None, aws_session_token=None,
-            region_name=None, profile_name=None):
+            region_name=None, profile_name=None, requester_pays=False):
         """Create a new boto3 session
 
         Parameters
@@ -137,7 +137,9 @@ def __init__(
             A region name, as per boto3.
         profile_name : str, optional
             A shared credentials profile name, as per boto3.
-
+        requester_pays : bool, optional
+            True if the requester agrees to pay transfer costs (default:
+            False)
         """
         import boto3
 
@@ -154,7 +156,8 @@ def __init__(
                     region_name=region_name,
                     profile_name=profile_name)
 
-        self._unsigned = aws_unsigned
+        self.requester_pays = requester_pays
+        self.unsigned = aws_unsigned
         self._creds = self._session._session.get_credentials()
 
     @property
@@ -169,6 +172,8 @@ def credentials(self):
             creds['aws_session_token'] = self._creds.token
         if self._session.region_name:
             creds['aws_region'] = self._session.region_name
+        if self.requester_pays:
+            creds['aws_request_payer'] = 'requester'
         return creds
 
     def get_credential_options(self):
@@ -179,7 +184,7 @@ def get_credential_options(self):
         dict
 
         """
-        if self._unsigned:
+        if self.unsigned:
             return {'AWS_NO_SIGN_REQUEST': 'YES'}
         else:
             return {k.upper(): v for k, v in self.credentials.items()}


Test patch:
diff --git a/tests/test_session.py b/tests/test_session.py
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -107,3 +107,10 @@ def test_foreign_session_factory_s3():
     assert isinstance(sesh, AWSSession)
     assert sesh._session.get_credentials().access_key == 'foo'
     assert sesh._session.get_credentials().secret_key == 'bar'
+
+
+def test_requester_pays():
+    """GDAL is configured with requester pays"""
+    sesh = AWSSession(requester_pays=True)
+    assert sesh._session
+    assert sesh.get_credential_options()['AWS_REQUEST_PAYER'] == 'requester'