Repository: boto/boto3
File: boto3/s3/transfer.py (Python)

Code before:
            raise ValueError(
                'Manager cannot be provided with client, config, '
                'nor osutil. These parameters are mutually exclusive.'
            )
        if config is None:
            config = TransferConfig()
        if osutil is None:
            osutil = OSUtils()
        if manager:
            self._manager = manager
        else:
            self._manager = create_transfer_manager(client, config, osutil)

    def upload_file(
        self, filename, bucket, key, callback=None, extra_args=None
    ):
        """Upload a file to an S3 object.

        Variants have also been injected into S3 client, Bucket and Object.
        You don't have to use S3Transfer.upload_file() directly.

        .. seealso::
            :py:meth:`S3.Client.upload_file`
            :py:meth:`S3.Client.upload_fileobj`
        """
        if isinstance(filename, Path):
            filename = fspath(filename)
        if not isinstance(filename, str):
            raise ValueError('Filename must be a string or a path-like object')

        subscribers = self._get_subscribers(callback)
        future = self._manager.upload(
            filename, bucket, key, extra_args, subscribers
        )
        try:
            future.result()
        # If a client error was raised, add the backwards compatibility layer
        # that raises a S3UploadFailedError. These specific errors were only
        # ever thrown for upload_parts but now can be thrown for any related
        # client error.
        except ClientError as e:
            raise S3UploadFailedError(
                "Failed to upload {} to {}: {}".format(
                    filename, '/'.join([bucket, key]), e
                )
            )

    def download_file(
        self, bucket, key, filename, extra_args=None, callback=None
    ):
        """Download an S3 object to a file.

Review comment (suggestion):
This only covers `pathlib.Path`, but not "path-like objects" as suggested by the exception message below. I think `os.PathLike` is what is needed here (but please double check).

Code after:
                'Manager cannot be provided with client, config, '
                'nor osutil. These parameters are mutually exclusive.'
            )
        if config is None:
            config = TransferConfig()
        if osutil is None:
            osutil = OSUtils()
        if manager:
            self._manager = manager
        else:
            self._manager = create_transfer_manager(client, config, osutil)

    def upload_file(
        self, filename, bucket, key, callback=None, extra_args=None
    ):
        """Upload a file to an S3 object.

        Variants have also been injected into S3 client, Bucket and Object.
        You don't have to use S3Transfer.upload_file() directly.

        .. seealso::
            :py:meth:`S3.Client.upload_file`
            :py:meth:`S3.Client.upload_fileobj`
        """
        if isinstance(filename, PathLike):
            filename = fspath(filename)
        if not isinstance(filename, str):
            raise ValueError('Filename must be a string or a path-like object')

        subscribers = self._get_subscribers(callback)
        future = self._manager.upload(
            filename, bucket, key, extra_args, subscribers
        )
        try:
            future.result()
        # If a client error was raised, add the backwards compatibility layer
        # that raises a S3UploadFailedError. These specific errors were only
        # ever thrown for upload_parts but now can be thrown for any related
        # client error.
        except ClientError as e:
            raise S3UploadFailedError(
                "Failed to upload {} to {}: {}".format(
                    filename, '/'.join([bucket, key]), e
                )
            )

    def download_file(
        self, bucket, key, filename, extra_args=None, callback=None
    ):
        """Download an S3 object to a file.