Merge pull request #1582 from rca/12756-missing-yaml-module-serializer-error-message

Fixed #12756: Improved error message when yaml module is missing.
This commit is contained in:
Russell Keith-Magee 2013-09-06 16:05:02 -07:00
commit 4f5faa1916
6 changed files with 111 additions and 20 deletions

View File

@ -58,6 +58,7 @@ answer newbie questions, and generally made Django that much better:
Gisle Aas <gisle@aas.no> Gisle Aas <gisle@aas.no>
Chris Adams Chris Adams
Mathieu Agopian <mathieu.agopian@gmail.com> Mathieu Agopian <mathieu.agopian@gmail.com>
Roberto Aguilar <roberto@baremetal.io>
ajs <adi@sieker.info> ajs <adi@sieker.info>
alang@bright-green.com alang@bright-green.com
A S Alam <aalam@users.sf.net> A S Alam <aalam@users.sf.net>

View File

@ -106,11 +106,11 @@ class Command(BaseCommand):
# Check that the serialization format exists; this is a shortcut to # Check that the serialization format exists; this is a shortcut to
# avoid collating all the objects and _then_ failing. # avoid collating all the objects and _then_ failing.
if format not in serializers.get_public_serializer_formats(): if format not in serializers.get_public_serializer_formats():
raise CommandError("Unknown serialization format: %s" % format) try:
serializers.get_serializer(format)
except serializers.SerializerDoesNotExist:
pass
try:
serializers.get_serializer(format)
except KeyError:
raise CommandError("Unknown serialization format: %s" % format) raise CommandError("Unknown serialization format: %s" % format)
def get_objects(): def get_objects():

View File

@ -27,17 +27,29 @@ BUILTIN_SERIALIZERS = {
"xml" : "django.core.serializers.xml_serializer", "xml" : "django.core.serializers.xml_serializer",
"python" : "django.core.serializers.python", "python" : "django.core.serializers.python",
"json" : "django.core.serializers.json", "json" : "django.core.serializers.json",
"yaml" : "django.core.serializers.pyyaml",
} }
# Check for PyYaml and register the serializer if it's available.
try:
import yaml
BUILTIN_SERIALIZERS["yaml"] = "django.core.serializers.pyyaml"
except ImportError:
pass
_serializers = {} _serializers = {}
class BadSerializer(object):
"""
Stub serializer to hold exception raised during registration
This allows the serializer registration to cache serializers and if there
is an error raised in the process of creating a serializer it will be
raised and passed along to the caller when the serializer is used.
"""
internal_use_only = False
def __init__(self, exception):
self.exception = exception
def __call__(self, *args, **kwargs):
raise self.exception
def register_serializer(format, serializer_module, serializers=None): def register_serializer(format, serializer_module, serializers=None):
"""Register a new serializer. """Register a new serializer.
@ -53,12 +65,23 @@ def register_serializer(format, serializer_module, serializers=None):
""" """
if serializers is None and not _serializers: if serializers is None and not _serializers:
_load_serializers() _load_serializers()
module = importlib.import_module(serializer_module)
try:
module = importlib.import_module(serializer_module)
except ImportError, exc:
bad_serializer = BadSerializer(exc)
module = type('BadSerializerModule', (object,), {
'Deserializer': bad_serializer,
'Serializer': bad_serializer,
})
if serializers is None: if serializers is None:
_serializers[format] = module _serializers[format] = module
else: else:
serializers[format] = module serializers[format] = module
def unregister_serializer(format): def unregister_serializer(format):
"Unregister a given serializer. This is not a thread-safe operation." "Unregister a given serializer. This is not a thread-safe operation."
if not _serializers: if not _serializers:

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import importlib
import json import json
from datetime import datetime from datetime import datetime
import re import re
@ -14,7 +15,7 @@ except ImportError:
from django.conf import settings from django.conf import settings
from django.core import serializers from django.core import management, serializers
from django.db import transaction, connection from django.db import transaction, connection
from django.test import TestCase, TransactionTestCase, Approximate from django.test import TestCase, TransactionTestCase, Approximate
from django.utils import six from django.utils import six
@ -440,6 +441,69 @@ class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, Transact
}]""" }]"""
YAML_IMPORT_ERROR_MESSAGE = r'No module named yaml'
class YamlImportModuleMock(object):
"""Provides a wrapped import_module function to simulate yaml ImportError
In order to run tests that verify the behavior of the YAML serializer
when run on a system that has yaml installed (like the django CI server),
mock import_module, so that it raises an ImportError when the yaml
serializer is being imported. The importlib.import_module() call is
being made in the serializers.register_serializer().
Refs: #12756
"""
def __init__(self):
self._import_module = importlib.import_module
def import_module(self, module_path):
if module_path == serializers.BUILTIN_SERIALIZERS['yaml']:
raise ImportError(YAML_IMPORT_ERROR_MESSAGE)
return self._import_module(module_path)
class NoYamlSerializerTestCase(TestCase):
"""Not having pyyaml installed provides a misleading error
Refs: #12756
"""
@classmethod
def setUpClass(cls):
"""Removes imported yaml and stubs importlib.import_module"""
super(NoYamlSerializerTestCase, cls).setUpClass()
cls._import_module_mock = YamlImportModuleMock()
importlib.import_module = cls._import_module_mock.import_module
# clear out cached serializers to emulate yaml missing
serializers._serializers = {}
@classmethod
def tearDownClass(cls):
"""Puts yaml back if necessary"""
super(NoYamlSerializerTestCase, cls).tearDownClass()
importlib.import_module = cls._import_module_mock._import_module
# clear out cached serializers to clean out BadSerializer instances
serializers._serializers = {}
def test_serializer_pyyaml_error_message(self):
"""Using yaml serializer without pyyaml raises ImportError"""
jane = Author(name="Jane")
self.assertRaises(ImportError, serializers.serialize, "yaml", [jane])
def test_deserializer_pyyaml_error_message(self):
"""Using yaml deserializer without pyyaml raises ImportError"""
self.assertRaises(ImportError, serializers.deserialize, "yaml", "")
def test_dumpdata_pyyaml_error_message(self):
"""Calling dumpdata produces an error when yaml package missing"""
self.assertRaisesRegexp(management.CommandError, YAML_IMPORT_ERROR_MESSAGE,
management.call_command, 'dumpdata', format='yaml')
@unittest.skipUnless(HAS_YAML, "No yaml library detected") @unittest.skipUnless(HAS_YAML, "No yaml library detected")
class YamlSerializerTestCase(SerializersTestBase, TestCase): class YamlSerializerTestCase(SerializersTestBase, TestCase):
serializer_name = "yaml" serializer_name = "yaml"

View File

@ -523,7 +523,10 @@ def streamTest(format, self):
else: else:
self.assertEqual(string_data, stream.content.decode('utf-8')) self.assertEqual(string_data, stream.content.decode('utf-8'))
for format in serializers.get_serializer_formats(): for format in [
f for f in serializers.get_serializer_formats()
if not isinstance(serializers.get_serializer(f), serializers.BadSerializer)
]:
setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format))
setattr(SerializerTests, 'test_' + format + '_natural_key_serializer', curry(naturalKeySerializerTest, format)) setattr(SerializerTests, 'test_' + format + '_natural_key_serializer', curry(naturalKeySerializerTest, format))
setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format)) setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))

View File

@ -599,7 +599,7 @@ class SerializationTests(TestCase):
obj = next(serializers.deserialize('xml', data)).object obj = next(serializers.deserialize('xml', data)).object
self.assertEqual(obj.dt, dt) self.assertEqual(obj.dt, dt)
if 'yaml' in serializers.get_serializer_formats(): if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
data = serializers.serialize('yaml', [Event(dt=dt)]) data = serializers.serialize('yaml', [Event(dt=dt)])
self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30") self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30")
obj = next(serializers.deserialize('yaml', data)).object obj = next(serializers.deserialize('yaml', data)).object
@ -623,7 +623,7 @@ class SerializationTests(TestCase):
obj = next(serializers.deserialize('xml', data)).object obj = next(serializers.deserialize('xml', data)).object
self.assertEqual(obj.dt, dt) self.assertEqual(obj.dt, dt)
if 'yaml' in serializers.get_serializer_formats(): if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
data = serializers.serialize('yaml', [Event(dt=dt)]) data = serializers.serialize('yaml', [Event(dt=dt)])
self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30.405060") self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30.405060")
obj = next(serializers.deserialize('yaml', data)).object obj = next(serializers.deserialize('yaml', data)).object
@ -647,7 +647,7 @@ class SerializationTests(TestCase):
obj = next(serializers.deserialize('xml', data)).object obj = next(serializers.deserialize('xml', data)).object
self.assertEqual(obj.dt, dt) self.assertEqual(obj.dt, dt)
if 'yaml' in serializers.get_serializer_formats(): if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
data = serializers.serialize('yaml', [Event(dt=dt)]) data = serializers.serialize('yaml', [Event(dt=dt)])
self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30.405060+07:00") self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30.405060+07:00")
obj = next(serializers.deserialize('yaml', data)).object obj = next(serializers.deserialize('yaml', data)).object
@ -671,7 +671,7 @@ class SerializationTests(TestCase):
obj = next(serializers.deserialize('xml', data)).object obj = next(serializers.deserialize('xml', data)).object
self.assertEqual(obj.dt, dt) self.assertEqual(obj.dt, dt)
if 'yaml' in serializers.get_serializer_formats(): if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
data = serializers.serialize('yaml', [Event(dt=dt)]) data = serializers.serialize('yaml', [Event(dt=dt)])
self.assert_yaml_contains_datetime(data, "2011-09-01 10:20:30+00:00") self.assert_yaml_contains_datetime(data, "2011-09-01 10:20:30+00:00")
obj = next(serializers.deserialize('yaml', data)).object obj = next(serializers.deserialize('yaml', data)).object
@ -695,7 +695,7 @@ class SerializationTests(TestCase):
obj = next(serializers.deserialize('xml', data)).object obj = next(serializers.deserialize('xml', data)).object
self.assertEqual(obj.dt, dt) self.assertEqual(obj.dt, dt)
if 'yaml' in serializers.get_serializer_formats(): if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
data = serializers.serialize('yaml', [Event(dt=dt)]) data = serializers.serialize('yaml', [Event(dt=dt)])
self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30+03:00") self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30+03:00")
obj = next(serializers.deserialize('yaml', data)).object obj = next(serializers.deserialize('yaml', data)).object
@ -719,7 +719,7 @@ class SerializationTests(TestCase):
obj = next(serializers.deserialize('xml', data)).object obj = next(serializers.deserialize('xml', data)).object
self.assertEqual(obj.dt, dt) self.assertEqual(obj.dt, dt)
if 'yaml' in serializers.get_serializer_formats(): if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
data = serializers.serialize('yaml', [Event(dt=dt)]) data = serializers.serialize('yaml', [Event(dt=dt)])
self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30+07:00") self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30+07:00")
obj = next(serializers.deserialize('yaml', data)).object obj = next(serializers.deserialize('yaml', data)).object