Authentication¶
For standard use, we recommend using environment variables to authenticate with the cloud storage services. This way, cloudpathlib
will be able to automatically read those credentials and authenticate without you needing to do anything else. Passing credentials via environment variables is also generally a security best practice for avoiding accidental sharing.
cloudpathlib
supports the standard environment variables used by each respective cloud service SDK.
Cloud | Environment Variables | SDK Documentation |
---|---|---|
Amazon S3 | AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY or AWS_PROFILE with credentials file |
Link |
Azure Blob Storage | AZURE_STORAGE_CONNECTION_STRING |
Link |
Google Cloud Storage | GOOGLE_APPLICATION_CREDENTIALS |
Link |
Advanced Use¶
The communication between cloudpathlib
and cloud storage services are handled by Client
objects. Each cloud storage service has its own Client
class implementation. See the linked API documentation pages for additional authentication options.
Cloud | Client | API Documentation |
---|---|---|
Amazon S3 | S3Client |
Link |
Azure Blob Storage | AzureBlobClient |
Link |
Google Cloud Storage | GSClient |
Link |
A client object holds the authenticated connection with a cloud service, as well as the configuration for the local cache. When you create instantiate a cloud path instance for the first time, a default client object is created for the respective cloud service.
from cloudpathlib import CloudPath
cloud_path = CloudPath("s3://cloudpathlib-test-bucket/") # same for S3Path(...)
cloud_path.client
#> <cloudpathlib.s3.s3client.S3Client at 0x7feac3d1fb90>
All subsequent instances of that service's cloud paths (in the example, all subsequent S3Path
instances) will reference the same client instance.
You can also explicitly instantiate a client instance. You will need to do so if you want to authenticate using any option other than the environment variables from the table in the previous section. (To see what those options are, check out the API documentation pages linked to in the table above.) You can then use that client instance's cloud path factory method, or pass it into a cloud path instantiation.
from cloudpathlib import S3Client
client = S3Client(aws_access_key_id="myaccesskey", aws_secret_access_key="mysecretkey")
# these next two commands are equivalent
# use client's factory method
cp1 = client.CloudPath("s3://cloudpathlib-test-bucket/")
# or pass client as keyword argument
cp2 = CloudPath("s3://cloudpathlib-test-bucket/", client=client)
If you have instantiated a client instance explicitly, you can also set it as the default client. Then, future cloud paths without a client specified will use that client instance.
client = S3Client(aws_access_key_id="myaccesskey", aws_secret_access_key="mysecretkey")
client.set_as_default_client()
If you need a reference to the default client:
S3Client.get_default_client()
#> <cloudpathlib.s3.s3client.S3Client at 0x7feac3d1fb90>
Accessing public S3 buckets without credentials¶
For most operations, you will need to have your S3 credentials configured. However, for buckets that provide public access, you can use cloudpathlib
without credentials. To do so, you need to instantiate a client and pass the kwarg no_sign_request=True
. Failure to do so will result in a NoCredentialsError
being thrown.
from cloudpathlib import CloudPath
# this file deinitely exists, but credentials are not configured
CloudPath("s3://ladi/Images/FEMA_CAP/2020/70349/DSC_0001_5a63d42e-27c6-448a-84f1-bfc632125b8e.jpg").exists()
#> NoCredentialsError
Instead, you must either configure credentials or instantiate a client object using no_sign_request=True
:
from cloudpathlib import S3Client
c = S3Client(no_sign_request=True)
# use this client object to create the CloudPath
c.CloudPath("s3://ladi/Images/FEMA_CAP/2020/70349/DSC_0001_5a63d42e-27c6-448a-84f1-bfc632125b8e.jpg").exists()
#> True
Note: Many public buckets do not allow listing of the bucket contents by anonymous users. If this is the case, any listing operation on a directory will fail with an error like ClientError: An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
when you try to do an operation, even with no_sign_request=True
. In this case, you can generally only work with CloudPath
objects that refer to the files themselves (instead of directories). You can contact the bucket owner to request that they allow listing, or write your code in a way that only references files you know will exist.
As noted above, you can also call .set_as_default_client()
on the client object that you create and then it will be used by default without your having to explicitly use the client object that you created.
Requester Pays buckets on S3¶
S3 supports Requester Pays buckets where you must have credentials to access the bucket and any costs are passed on to you rather than the owner of the bucket.
For a requester pays bucket, you need to pass extras telling cloudpathlib you will pay for any operations.
For example, on the requester pays bucket arxiv
, just trying to list the contents will result in a ClientError
:
from cloudpathlib import CloudPath
tars = list(CloudPath("s3://arxiv/src/").iterdir())
print(tars)
#> ClientError: An error occurred (AccessDenied) ...
To indicate that the request payer will be the "requester," pass the extra args to an S3Client
and use that client to instantiate paths:
from cloudpathlib import S3Client
c = S3Client(extra_args={"RequestPayer": "requester"})
# use the client we created to build the path
tars = list(c.CloudPath("s3://arxiv/src/").iterdir())
print(tars)
As noted above, you can also call .set_as_default_client()
on the client object that you create and then it will be used by default without your having to explicitly use the client object that you created.
Other S3 ExtraArgs
in boto3
¶
The S3 SDK, boto3
supports a set of ExtraArgs
for uploads, downloads, and listing operations. When you instatiate a client, you can pass the extra_args
keyword argument with any of those extra args that you want to set. We will pass these on to the upload, download, and list methods insofar as those methods support the specific args.
The args supported for uploads are the same as boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS
, see the boto3
documentation for the latest, but as of the time of writing, these are:
ACL
CacheControl
ChecksumAlgorithm
ContentDisposition
ContentEncoding
ContentLanguage
ContentType
ExpectedBucketOwner
Expires
GrantFullControl
GrantRead
GrantReadACP
GrantWriteACP
Metadata
ObjectLockLegalHoldStatus
ObjectLockMode
ObjectLockRetainUntilDate
RequestPayer
ServerSideEncryption
StorageClass
SSECustomerAlgorithm
SSECustomerKey
SSECustomerKeyMD5
SSEKMSKeyId
SSEKMSEncryptionContext
Tagging
WebsiteRedirectLocation
The args supported for downloads are the same as boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS
, see the boto3
documentation for the latest, but as of the time of writing, these are:
ChecksumMode
VersionId
SSECustomerAlgorithm
SSECustomerKey
SSECustomerKeyMD5
RequestPayer
ExpectedBucketOwner
To use any of these extra args, pass them as a dict to extra_args
when instantiating and S3Client
.
from cloudpathlib import S3Client
c = S3Client(extra_args={
"ChecksumMode": "ENABLED", # download extra arg, only used when downloading
"ACL": "public-read", # upload extra arg, only used when uploading
})
# use these extras for all CloudPaths
c.set_as_default_client()
Note: The extra_args
kwargs accepts the union of upload and download args, and will only pass on the relevant subset to the boto3
method that is called by the internals of S3Client
.
Note: The ExtraArgs on the client will be used for every call that client makes. If you need to set different ExtraArgs
in different code paths, we recommend creating separate explicit client objects and using those to create and manage the CloudPath objects with different needs.
Note: To explicitly set the ContentType
and ContentEncoding
, we recommend using the content_type_method
kwarg when instantiating the client. If instead you want to set this for all uploads via the extras, you must additionally pass content_type_method=None
to the S3Client
so we don't try to guess these automatically.
Accessing custom S3-compatible object stores¶
It might happen so that you need to access a customly deployed S3 object store (MinIO, Ceph or any other).
In such cases, the service endpoint will be different from the AWS object store endpoints (used by default).
To specify a custom endpoint address, you will need to manually instantiate Client
with the endpoint_url
parameter,
provinding http/https URL including port.
from cloudpathlib import S3Client, CloudPath
# create a client pointing to the endpoint
client = S3Client(endpoint_url="http://my.s3.server:1234")
# option 1: use the client to create paths
cp1 = client.CloudPath("s3://cloudpathlib-test-bucket/")
# option 2: pass the client as keyword argument
cp2 = CloudPath("s3://cloudpathlib-test-bucket/", client=client)
# option3: set this client as the default so it is used in any future paths
client.set_as_default_client()
cp3 = CloudPath("s3://cloudpathlib-test-bucket/")
Pickling CloudPath
objects¶
You can pickle and unpickle CloudPath
objects normally, for example:
from pathlib import Path
import pickle
from cloudpathlib import CloudPath
with Path("cloud_path.pkl").open("wb") as f:
pickle.dump(CloudPath("s3://my-awesome-bucket/cool-file.txt"), f)
with Path("cloud_path.pkl").open("rb") as f:
pickled = pickle.load(f)
assert pickled.bucket == "my-awesome-bucket"
The associated client
, however, is not pickled. When a CloudPath
is
unpickled, the client on the unpickled object will be set to the default
client for that class.
For example, this will not work:
from pathlib import Path
import pickle
from cloudpathlib import S3Client, CloudPath
# create a custom client pointing to the endpoint
client = S3Client(endpoint_url="http://my.s3.server:1234")
# use that client when creating a cloud path
p = CloudPath("s3://cloudpathlib-test-bucket/cool_file.txt", client=client)
p.write_text("hello!")
with Path("cloud_path.pkl").open("wb") as f:
pickle.dump(p, f)
with Path("cloud_path.pkl").open("rb") as f:
pickled = pickle.load(f)
# this will be False, because it will use the default `S3Client`
assert pickled.exists() == False
To get this to work, you need to set the custom client
to the default
before unpickling:
# set the custom client as the default before unpickling
client.set_as_default_client()
with ("cloud_path.pkl").open("rb") as f:
pickled2 = pickle.load(f)
assert pickled2.exists()
assert pickled2.client == client