Repository: minio/minio-py

Issue:
remove_objects generates invalid XML
The `remove_objects` method generates a XML request which includes the following: `<Quiet>True</Quiet>`. I suspect the boolean value should not be capitalized, it should be `true`.

The [AWS documentation for the DeleteObjects call](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html) describes the "Quiet" field as boolean.

The [XML schema spec](https://www.w3.org/TR/xmlschema-2/#boolean) says the boolean datatype should have lowercase values – true|false.

I've been testing minio-py with Scaleway and OVH object storage, and both rejected the uppercase `True`. When I patched my local minio-py copy to not put the `Quiet` field in the request XML, the remove_objects calls worked as expected with both Scaleway and OVH. 

PS. After I reported the issue, Scaleway have already fixed their S3 interface to accept `<Quiet>True</Quiet>`, and OVH is working on it. Still, it would be good to fix minio-py to send lowercase true|false, so it works with with other S3 implementations too.


Discussion:

Patch:
diff --git a/Makefile b/Makefile
index 1c61199..ee466b8 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ default: tests
 
 getdeps:
 	@echo "Installing required dependencies"
-	@pip install --user --upgrade autopep8 certifi mock pytest pylint urllib3
+	@pip install --user --upgrade autopep8 certifi pytest pylint urllib3
 
 check: getdeps
 	@echo "Running checks"
diff --git a/minio/__init__.py b/minio/__init__.py
index e3177d8..41a5659 100644
--- a/minio/__init__.py
+++ b/minio/__init__.py
@@ -33,7 +33,7 @@ minio - MinIO Python SDK for Amazon S3 Compatible Cloud Storage
 
 __title__ = "minio-py"
 __author__ = "MinIO, Inc."
-__version__ = "7.1.6"
+__version__ = "7.1.7"
 __license__ = "Apache 2.0"
 __copyright__ = "Copyright 2015, 2016, 2017, 2018, 2019, 2020 MinIO, Inc."
 
diff --git a/minio/deleteobjects.py b/minio/deleteobjects.py
index d4959df..aadbbae 100644
--- a/minio/deleteobjects.py
+++ b/minio/deleteobjects.py
@@ -48,7 +48,7 @@ class DeleteRequest:
         """Convert to XML."""
         element = Element("Delete")
         if self._quiet:
-            SubElement(element, "Quiet", str(self._quiet))
+            SubElement(element, "Quiet", "true")
         for obj in self._object_list:
             obj.toxml(element)
         return element
diff --git a/minio/error.py b/minio/error.py
index 57082bd..7b2dfe6 100644
--- a/minio/error.py
+++ b/minio/error.py
@@ -49,6 +49,9 @@ class InvalidResponseError(MinioException):
             f"Response code: {code}, Content-Type: {content_type}, Body: {body}"
         )
 
+    def __reduce__(self):
+        return type(self), (self._code, self._content_type, self._body)
+
 
 class ServerError(MinioException):
     """Raised to indicate that S3 service returning HTTP server error."""
@@ -85,6 +88,11 @@ class S3Error(MinioException):
             f"host_id: {host_id}{bucket_message}{object_message}"
         )
 
+    def __reduce__(self):
+        return type(self), (self._code, self._message, self._resource,
+                            self._request_id, self._host_id, self._response,
+                            self._bucket_name, self._object_name)
+
     @property
     def code(self):
         """Get S3 error code."""
diff --git a/minio/lifecycleconfig.py b/minio/lifecycleconfig.py
index ef5b387..3cabcc8 100644
--- a/minio/lifecycleconfig.py
+++ b/minio/lifecycleconfig.py
@@ -200,7 +200,7 @@ class Expiration(DateDays):
             SubElement(
                 element,
                 "ExpiredObjectDeleteMarker",
-                str(self._expired_object_delete_marker),
+                str(self._expired_object_delete_marker).lower(),
             )
         return element
 
diff --git a/setup.py b/setup.py
index 0af0ac4..c785a22 100644
--- a/setup.py
+++ b/setup.py
@@ -44,7 +44,7 @@ setup(
     package_dir={"minio": "minio"},
     packages=["minio", "minio.credentials"],
     install_requires=["certifi", "urllib3"],
-    tests_require=["mock"],
+    tests_require=[],
     license="Apache License 2.0",
     classifiers=[
         "Development Status :: 5 - Production/Stable",


Test patch:
diff --git a/tests/unit/bucket_exist_test.py b/tests/unit/bucket_exist_test.py
index 935da35..b0d5202 100644
--- a/tests/unit/bucket_exist_test.py
+++ b/tests/unit/bucket_exist_test.py
@@ -16,7 +16,7 @@
 
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/credentials_test.py b/tests/unit/credentials_test.py
index b04f028..a66c8cf 100644
--- a/tests/unit/credentials_test.py
+++ b/tests/unit/credentials_test.py
@@ -19,7 +19,7 @@ import os
 from datetime import datetime, timedelta
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio.credentials.credentials import Credentials
 from minio.credentials.providers import (AWSConfigProvider, ChainedProvider,
diff --git a/tests/unit/get_bucket_policy_test.py b/tests/unit/get_bucket_policy_test.py
index 69df76f..81e8380 100644
--- a/tests/unit/get_bucket_policy_test.py
+++ b/tests/unit/get_bucket_policy_test.py
@@ -18,7 +18,7 @@
 import json
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/get_object_test.py b/tests/unit/get_object_test.py
index 9b5fea4..f7edff7 100644
--- a/tests/unit/get_object_test.py
+++ b/tests/unit/get_object_test.py
@@ -16,7 +16,7 @@
 
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/list_buckets_test.py b/tests/unit/list_buckets_test.py
index c5d6d54..8fe83db 100644
--- a/tests/unit/list_buckets_test.py
+++ b/tests/unit/list_buckets_test.py
@@ -17,7 +17,7 @@
 from datetime import datetime, timezone
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/list_objects_test.py b/tests/unit/list_objects_test.py
index 7c8be6b..a0aaf61 100644
--- a/tests/unit/list_objects_test.py
+++ b/tests/unit/list_objects_test.py
@@ -17,7 +17,7 @@
 import time
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/list_objects_v1_test.py b/tests/unit/list_objects_v1_test.py
index 4875399..758f8a5 100644
--- a/tests/unit/list_objects_v1_test.py
+++ b/tests/unit/list_objects_v1_test.py
@@ -17,7 +17,7 @@
 import time
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/make_bucket_test.py b/tests/unit/make_bucket_test.py
index a7ebca9..7a54553 100644
--- a/tests/unit/make_bucket_test.py
+++ b/tests/unit/make_bucket_test.py
@@ -16,7 +16,7 @@
 
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/presigned_get_object_test.py b/tests/unit/presigned_get_object_test.py
index 01443b6..b3b25a8 100644
--- a/tests/unit/presigned_get_object_test.py
+++ b/tests/unit/presigned_get_object_test.py
@@ -17,7 +17,7 @@
 from datetime import timedelta
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 
diff --git a/tests/unit/remove_bucket_test.py b/tests/unit/remove_bucket_test.py
index 5490d2b..1443691 100644
--- a/tests/unit/remove_bucket_test.py
+++ b/tests/unit/remove_bucket_test.py
@@ -16,7 +16,7 @@
 
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/remove_object_test.py b/tests/unit/remove_object_test.py
index 747399d..6a30f6d 100644
--- a/tests/unit/remove_object_test.py
+++ b/tests/unit/remove_object_test.py
@@ -16,7 +16,7 @@
 
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
diff --git a/tests/unit/remove_objects_test.py b/tests/unit/remove_objects_test.py
index 0eba138..a3cfaf7 100644
--- a/tests/unit/remove_objects_test.py
+++ b/tests/unit/remove_objects_test.py
@@ -17,7 +17,7 @@
 import itertools
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT
@@ -35,7 +35,7 @@ class RemoveObjectsTest(TestCase):
             MockResponse('POST',
                          'https://localhost:9000/hello?delete=',
                          {'User-Agent': _DEFAULT_USER_AGENT,
-                          'Content-Md5': u'YcTFWle4oiLJ6sT95FwpdA=='}, 200,
+                          'Content-Md5': u'Te1kmIjQRNNz70DJjsrD8A=='}, 200,
                          content=b'<Delete/>')
         )
         client = Minio('localhost:9000')
@@ -53,7 +53,7 @@ class RemoveObjectsTest(TestCase):
             MockResponse('POST',
                          'https://localhost:9000/hello?delete=',
                          {'User-Agent': _DEFAULT_USER_AGENT,
-                          'Content-Md5': u'YcTFWle4oiLJ6sT95FwpdA=='}, 200,
+                          'Content-Md5': u'Te1kmIjQRNNz70DJjsrD8A=='}, 200,
                          content=b'<Delete/>')
         )
         client = Minio('localhost:9000')
@@ -71,7 +71,7 @@ class RemoveObjectsTest(TestCase):
             MockResponse('POST',
                          'https://localhost:9000/hello?delete=',
                          {'User-Agent': _DEFAULT_USER_AGENT,
-                          'Content-Md5': u'YcTFWle4oiLJ6sT95FwpdA=='}, 200,
+                          'Content-Md5': u'Te1kmIjQRNNz70DJjsrD8A=='}, 200,
                          content=b'<Delete/>')
         )
         client = Minio('localhost:9000')
diff --git a/tests/unit/stat_object_test.py b/tests/unit/stat_object_test.py
index 8736335..cf91759 100644
--- a/tests/unit/stat_object_test.py
+++ b/tests/unit/stat_object_test.py
@@ -16,7 +16,7 @@
 
 from unittest import TestCase
 
-import mock
+import unittest.mock as mock
 
 from minio import Minio
 from minio.api import _DEFAULT_USER_AGENT