Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #1582 from rca/12756-missing-yaml-module-serialize…

…r-error-message

Fixed #12756: Improved error message when yaml module is missing.
  • Loading branch information...
commit 4f5faa1916e7c8cb72cc9ebf1a1fd964ba6e707b 2 parents 9b2dc12 + 01a5359
Russell Keith-Magee authored September 06, 2013
1  AUTHORS
@@ -58,6 +58,7 @@ answer newbie questions, and generally made Django that much better:
58 58
     Gisle Aas <gisle@aas.no>
59 59
     Chris Adams
60 60
     Mathieu Agopian <mathieu.agopian@gmail.com>
  61
+    Roberto Aguilar <roberto@baremetal.io>
61 62
     ajs <adi@sieker.info>
62 63
     alang@bright-green.com
63 64
     A S Alam <aalam@users.sf.net>
8  django/core/management/commands/dumpdata.py
@@ -106,11 +106,11 @@ def handle(self, *app_labels, **options):
106 106
         # Check that the serialization format exists; this is a shortcut to
107 107
         # avoid collating all the objects and _then_ failing.
108 108
         if format not in serializers.get_public_serializer_formats():
109  
-            raise CommandError("Unknown serialization format: %s" % format)
  109
+            try:
  110
+                serializers.get_serializer(format)
  111
+            except serializers.SerializerDoesNotExist:
  112
+                pass
110 113
 
111  
-        try:
112  
-            serializers.get_serializer(format)
113  
-        except KeyError:
114 114
             raise CommandError("Unknown serialization format: %s" % format)
115 115
 
116 116
         def get_objects():
39  django/core/serializers/__init__.py
@@ -27,17 +27,29 @@
27 27
     "xml"    : "django.core.serializers.xml_serializer",
28 28
     "python" : "django.core.serializers.python",
29 29
     "json"   : "django.core.serializers.json",
  30
+    "yaml"   : "django.core.serializers.pyyaml",
30 31
 }
31 32
 
32  
-# Check for PyYaml and register the serializer if it's available.
33  
-try:
34  
-    import yaml
35  
-    BUILTIN_SERIALIZERS["yaml"] = "django.core.serializers.pyyaml"
36  
-except ImportError:
37  
-    pass
38  
-
39 33
 _serializers = {}
40 34
 
  35
+
  36
+class BadSerializer(object):
  37
+    """
  38
+    Stub serializer to hold exception raised during registration
  39
+
  40
+    This allows the serializer registration to cache serializers and if there
  41
+    is an error raised in the process of creating a serializer it will be
  42
+    raised and passed along to the caller when the serializer is used.
  43
+    """
  44
+    internal_use_only = False
  45
+
  46
+    def __init__(self, exception):
  47
+        self.exception = exception
  48
+
  49
+    def __call__(self, *args, **kwargs):
  50
+        raise self.exception
  51
+
  52
+
41 53
 def register_serializer(format, serializer_module, serializers=None):
42 54
     """Register a new serializer.
43 55
 
@@ -53,12 +65,23 @@ def register_serializer(format, serializer_module, serializers=None):
53 65
     """
54 66
     if serializers is None and not _serializers:
55 67
         _load_serializers()
56  
-    module = importlib.import_module(serializer_module)
  68
+
  69
+    try:
  70
+        module = importlib.import_module(serializer_module)
  71
+    except ImportError, exc:
  72
+        bad_serializer = BadSerializer(exc)
  73
+
  74
+        module = type('BadSerializerModule', (object,), {
  75
+            'Deserializer': bad_serializer,
  76
+            'Serializer': bad_serializer,
  77
+        })
  78
+
57 79
     if serializers is None:
58 80
         _serializers[format] = module
59 81
     else:
60 82
         serializers[format] = module
61 83
 
  84
+
62 85
 def unregister_serializer(format):
63 86
     "Unregister a given serializer. This is not a thread-safe operation."
64 87
     if not _serializers:
66  tests/serializers/tests.py
... ...
@@ -1,6 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 from __future__ import unicode_literals
3 3
 
  4
+import importlib
4 5
 import json
5 6
 from datetime import datetime
6 7
 import re
@@ -14,7 +15,7 @@
14 15
 
15 16
 
16 17
 from django.conf import settings
17  
-from django.core import serializers
  18
+from django.core import management, serializers
18 19
 from django.db import transaction, connection
19 20
 from django.test import TestCase, TransactionTestCase, Approximate
20 21
 from django.utils import six
@@ -440,6 +441,69 @@ class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, Transact
440 441
     }]"""
441 442
 
442 443
 
  444
+YAML_IMPORT_ERROR_MESSAGE = r'No module named yaml'
  445
+class YamlImportModuleMock(object):
  446
+    """Provides a wrapped import_module function to simulate yaml ImportError
  447
+
  448
+    In order to run tests that verify the behavior of the YAML serializer
  449
+    when run on a system that has yaml installed (like the django CI server),
  450
+    mock import_module, so that it raises an ImportError when the yaml
  451
+    serializer is being imported.  The importlib.import_module() call is
  452
+    being made in the serializers.register_serializer().
  453
+
  454
+    Refs: #12756
  455
+    """
  456
+    def __init__(self):
  457
+        self._import_module = importlib.import_module
  458
+
  459
+    def import_module(self, module_path):
  460
+        if module_path == serializers.BUILTIN_SERIALIZERS['yaml']:
  461
+            raise ImportError(YAML_IMPORT_ERROR_MESSAGE)
  462
+
  463
+        return self._import_module(module_path)
  464
+
  465
+
  466
+class NoYamlSerializerTestCase(TestCase):
  467
+    """Not having pyyaml installed provides a misleading error
  468
+
  469
+    Refs: #12756
  470
+    """
  471
+    @classmethod
  472
+    def setUpClass(cls):
  473
+        """Removes imported yaml and stubs importlib.import_module"""
  474
+        super(NoYamlSerializerTestCase, cls).setUpClass()
  475
+
  476
+        cls._import_module_mock = YamlImportModuleMock()
  477
+        importlib.import_module = cls._import_module_mock.import_module
  478
+
  479
+        # clear out cached serializers to emulate yaml missing
  480
+        serializers._serializers = {}
  481
+
  482
+    @classmethod
  483
+    def tearDownClass(cls):
  484
+        """Puts yaml back if necessary"""
  485
+        super(NoYamlSerializerTestCase, cls).tearDownClass()
  486
+
  487
+        importlib.import_module = cls._import_module_mock._import_module
  488
+
  489
+        # clear out cached serializers to clean out BadSerializer instances
  490
+        serializers._serializers = {}
  491
+
  492
+    def test_serializer_pyyaml_error_message(self):
  493
+        """Using yaml serializer without pyyaml raises ImportError"""
  494
+        jane = Author(name="Jane")
  495
+        self.assertRaises(ImportError, serializers.serialize, "yaml", [jane])
  496
+
  497
+    def test_deserializer_pyyaml_error_message(self):
  498
+        """Using yaml deserializer without pyyaml raises ImportError"""
  499
+        self.assertRaises(ImportError, serializers.deserialize, "yaml", "")
  500
+
  501
+    def test_dumpdata_pyyaml_error_message(self):
  502
+        """Calling dumpdata produces an error when yaml package missing"""
  503
+        self.assertRaisesRegexp(management.CommandError, YAML_IMPORT_ERROR_MESSAGE,
  504
+                management.call_command, 'dumpdata', format='yaml')
  505
+
  506
+
443 507
 @unittest.skipUnless(HAS_YAML, "No yaml library detected")
444 508
 class YamlSerializerTestCase(SerializersTestBase, TestCase):
445 509
     serializer_name = "yaml"
5  tests/serializers_regress/tests.py
@@ -523,7 +523,10 @@ def streamTest(format, self):
523 523
         else:
524 524
             self.assertEqual(string_data, stream.content.decode('utf-8'))
525 525
 
526  
-for format in serializers.get_serializer_formats():
  526
+for format in [
  527
+            f for f in serializers.get_serializer_formats()
  528
+            if not isinstance(serializers.get_serializer(f), serializers.BadSerializer)
  529
+        ]:
527 530
     setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format))
528 531
     setattr(SerializerTests, 'test_' + format + '_natural_key_serializer', curry(naturalKeySerializerTest, format))
529 532
     setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))
12  tests/timezones/tests.py
@@ -599,7 +599,7 @@ def test_naive_datetime(self):
599 599
         obj = next(serializers.deserialize('xml', data)).object
600 600
         self.assertEqual(obj.dt, dt)
601 601
 
602  
-        if 'yaml' in serializers.get_serializer_formats():
  602
+        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
603 603
             data = serializers.serialize('yaml', [Event(dt=dt)])
604 604
             self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30")
605 605
             obj = next(serializers.deserialize('yaml', data)).object
@@ -623,7 +623,7 @@ def test_naive_datetime_with_microsecond(self):
623 623
         obj = next(serializers.deserialize('xml', data)).object
624 624
         self.assertEqual(obj.dt, dt)
625 625
 
626  
-        if 'yaml' in serializers.get_serializer_formats():
  626
+        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
627 627
             data = serializers.serialize('yaml', [Event(dt=dt)])
628 628
             self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30.405060")
629 629
             obj = next(serializers.deserialize('yaml', data)).object
@@ -647,7 +647,7 @@ def test_aware_datetime_with_microsecond(self):
647 647
         obj = next(serializers.deserialize('xml', data)).object
648 648
         self.assertEqual(obj.dt, dt)
649 649
 
650  
-        if 'yaml' in serializers.get_serializer_formats():
  650
+        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
651 651
             data = serializers.serialize('yaml', [Event(dt=dt)])
652 652
             self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30.405060+07:00")
653 653
             obj = next(serializers.deserialize('yaml', data)).object
@@ -671,7 +671,7 @@ def test_aware_datetime_in_utc(self):
671 671
         obj = next(serializers.deserialize('xml', data)).object
672 672
         self.assertEqual(obj.dt, dt)
673 673
 
674  
-        if 'yaml' in serializers.get_serializer_formats():
  674
+        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
675 675
             data = serializers.serialize('yaml', [Event(dt=dt)])
676 676
             self.assert_yaml_contains_datetime(data, "2011-09-01 10:20:30+00:00")
677 677
             obj = next(serializers.deserialize('yaml', data)).object
@@ -695,7 +695,7 @@ def test_aware_datetime_in_local_timezone(self):
695 695
         obj = next(serializers.deserialize('xml', data)).object
696 696
         self.assertEqual(obj.dt, dt)
697 697
 
698  
-        if 'yaml' in serializers.get_serializer_formats():
  698
+        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
699 699
             data = serializers.serialize('yaml', [Event(dt=dt)])
700 700
             self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30+03:00")
701 701
             obj = next(serializers.deserialize('yaml', data)).object
@@ -719,7 +719,7 @@ def test_aware_datetime_in_other_timezone(self):
719 719
         obj = next(serializers.deserialize('xml', data)).object
720 720
         self.assertEqual(obj.dt, dt)
721 721
 
722  
-        if 'yaml' in serializers.get_serializer_formats():
  722
+        if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer):
723 723
             data = serializers.serialize('yaml', [Event(dt=dt)])
724 724
             self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30+07:00")
725 725
             obj = next(serializers.deserialize('yaml', data)).object

0 notes on commit 4f5faa1

Please sign in to comment.
Something went wrong with that request. Please try again.