Repository: getmoto/moto

Issue:
S3 object tagging causes `TypeError: string indices must be integers`
Code to reproduce:

```
import boto3
from moto import mock_s3


@mock_s3
def main():

    data = 'some data'
    client = boto3.client('s3')
    bucket = 'somebucketname'
    key = 'somekey'
    client.create_bucket(Bucket=bucket)
    client.put_object(Bucket=bucket, Key=key, Body=data)
    tags = client.get_object_tagging(Bucket=bucket, Key=key)['TagSet']
    tags.append({"Key": "is_deleted", "Value": "True"})
    # the following line causes the exception
    client.put_object_tagging(Bucket=bucket, Key=key, Tagging={'TagSet': tags})


if __name__ == '__main__':
    main()

```

Traceback:

```
Traceback (most recent call last):
  File "/Users/mklosi/Library/Application Support/IntelliJIdea2017.2/python/helpers/pydev/pydevd.py", line 1599, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/Users/mklosi/Library/Application Support/IntelliJIdea2017.2/python/helpers/pydev/pydevd.py", line 1026, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Users/mklosi/projects/my_project/testing/moto_testing.py", line 21, in <module>
    main()
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/moto/core/models.py", line 70, in wrapper
    result = func(*args, **kwargs)
  File "/Users/mklosi/projects/my_project/testing/moto_testing.py", line 16, in main
    client.put_object_tagging(Bucket=bucket, Key=key, Tagging={'TagSet': tags})
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/client.py", line 312, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/client.py", line 592, in _make_api_call
    operation_model, request_dict)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/endpoint.py", line 141, in make_request
    return self._send_request(request_dict, operation_model)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/endpoint.py", line 170, in _send_request
    success_response, exception):
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/endpoint.py", line 249, in _needs_retry
    caught_exception=caught_exception, request_dict=request_dict)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/hooks.py", line 227, in emit
    return self._emit(event_name, kwargs)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/hooks.py", line 210, in _emit
    response = handler(**kwargs)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/retryhandler.py", line 183, in __call__
    if self._checker(attempts, response, caught_exception):
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/retryhandler.py", line 251, in __call__
    caught_exception)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/retryhandler.py", line 269, in _should_retry
    return self._checker(attempt_number, response, caught_exception)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/retryhandler.py", line 317, in __call__
    caught_exception)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/retryhandler.py", line 223, in __call__
    attempt_number, caught_exception)
  File "/Users/mklosi/projects/my_project/.tox/py27/lib/python2.7/site-packages/botocore/retryhandler.py", line 359, in _check_caught_exception
    raise caught_exception
TypeError: string indices must be integers

```


Discussion:

Resolving patch:
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -764,7 +764,7 @@ def _tagging_from_headers(self, headers):
             return FakeTagging()
 
     def _tagging_from_xml(self, xml):
-        parsed_xml = xmltodict.parse(xml)
+        parsed_xml = xmltodict.parse(xml, force_list={'Tag': True})
 
         tags = []
         for tag in parsed_xml['Tagging']['TagSet']['Tag']:


Test patch:
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -1775,6 +1775,30 @@ def test_boto3_put_object_tagging():
     resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
 
 
+@mock_s3
+def test_boto3_put_object_tagging_with_single_tag():
+    s3 = boto3.client('s3', region_name='us-east-1')
+    bucket_name = 'mybucket'
+    key = 'key-with-tags'
+    s3.create_bucket(Bucket=bucket_name)
+
+    s3.put_object(
+        Bucket=bucket_name,
+        Key=key,
+        Body='test'
+    )
+
+    resp = s3.put_object_tagging(
+        Bucket=bucket_name,
+        Key=key,
+        Tagging={'TagSet': [
+            {'Key': 'item1', 'Value': 'foo'}
+        ]}
+    )
+
+    resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
+
+
 @mock_s3
 def test_boto3_get_object_tagging():
     s3 = boto3.client('s3', region_name='us-east-1')