Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[1.6.x] Merge pull request #1582 from rca/12756-missing-yaml-module-s…

…erializer-error-message

Fixed #12756: Improved error message when yaml module is missing.

Backport of 4f5faa1 from master.
  • Loading branch information...
commit 3df9647ad96eb0f1919be921bb96e949f1a518a0 1 parent 99952ba
Russell Keith-Magee authored September 06, 2013
1  AUTHORS
@@ -57,6 +57,7 @@ answer newbie questions, and generally made Django that much better:
57 57
     Gisle Aas <gisle@aas.no>
58 58
     Chris Adams
59 59
     Mathieu Agopian <mathieu.agopian@gmail.com>
  60
+    Roberto Aguilar <roberto@baremetal.io>
60 61
     ajs <adi@sieker.info>
61 62
     alang@bright-green.com
62 63
     A S Alam <aalam@users.sf.net>
8  django/core/management/commands/dumpdata.py
@@ -105,11 +105,11 @@ def handle(self, *app_labels, **options):
105 105
         # Check that the serialization format exists; this is a shortcut to
106 106
         # avoid collating all the objects and _then_ failing.
107 107
         if format not in serializers.get_public_serializer_formats():
108  
-            raise CommandError("Unknown serialization format: %s" % format)
  108
+            try:
  109
+                serializers.get_serializer(format)
  110
+            except serializers.SerializerDoesNotExist:
  111
+                pass
109 112
 
110  
-        try:
111  
-            serializers.get_serializer(format)
112  
-        except KeyError:
113 113
             raise CommandError("Unknown serialization format: %s" % format)
114 114
 
115 115
         def get_objects():
39  django/core/serializers/__init__.py
@@ -26,17 +26,29 @@
26 26
     "xml"    : "django.core.serializers.xml_serializer",
27 27
     "python" : "django.core.serializers.python",
28 28
     "json"   : "django.core.serializers.json",
  29
+    "yaml"   : "django.core.serializers.pyyaml",
29 30
 }
30 31
 
31  
-# Check for PyYaml and register the serializer if it's available.
32  
-try:
33  
-    import yaml
34  
-    BUILTIN_SERIALIZERS["yaml"] = "django.core.serializers.pyyaml"
35  
-except ImportError:
36  
-    pass
37  
-
38 32
 _serializers = {}
39 33
 
  34
+
  35
+class BadSerializer(object):
  36
+    """
  37
+    Stub serializer to hold exception raised during registration
  38
+
  39
+    This allows the serializer registration to cache serializers and if there
  40
+    is an error raised in the process of creating a serializer it will be
  41
+    raised and passed along to the caller when the serializer is used.
  42
+    """
  43
+    internal_use_only = False
  44
+
  45
+    def __init__(self, exception):
  46
+        self.exception = exception
  47
+
  48
+    def __call__(self, *args, **kwargs):
  49
+        raise self.exception
  50
+
  51
+
40 52
 def register_serializer(format, serializer_module, serializers=None):
41 53
     """Register a new serializer.
42 54
 
@@ -52,12 +64,23 @@ def register_serializer(format, serializer_module, serializers=None):
52 64
     """
53 65
     if serializers is None and not _serializers:
54 66
         _load_serializers()
55  
-    module = importlib.import_module(serializer_module)
  67
+
  68
+    try:
  69
+        module = importlib.import_module(serializer_module)
  70
+    except ImportError, exc:
  71
+        bad_serializer = BadSerializer(exc)
  72
+
  73
+        module = type('BadSerializerModule', (object,), {
  74
+            'Deserializer': bad_serializer,
  75
+            'Serializer': bad_serializer,
  76
+        })
  77
+
56 78
     if serializers is None:
57 79
         _serializers[format] = module
58 80
     else:
59 81
         serializers[format] = module
60 82
 
  83
+
61 84
 def unregister_serializer(format):
62 85
     "Unregister a given serializer. This is not a thread-safe operation."
63 86
     if not _serializers:
163  tests/serializers/tests.py
... ...
@@ -1,17 +1,24 @@
  1
+# -*- coding: utf-8 -*-
1 2
 from __future__ import absolute_import, unicode_literals
2 3
 
3  
-# -*- coding: utf-8 -*-
4 4
 import json
5 5
 from datetime import datetime
6 6
 from xml.dom import minidom
7 7
 
  8
+try:
  9
+    import yaml
  10
+    HAS_YAML = True
  11
+except ImportError:
  12
+    HAS_YAML = False
  13
+
8 14
 from django.conf import settings
9  
-from django.core import serializers
  15
+from django.core import management, serializers
10 16
 from django.db import transaction, connection
11 17
 from django.test import TestCase, TransactionTestCase, Approximate
12 18
 from django.utils import six
13 19
 from django.utils.six import StringIO
14 20
 from django.utils import unittest
  21
+from django.utils import importlib
15 22
 
16 23
 from .models import (Category, Author, Article, AuthorProfile, Actor, Movie,
17 24
     Score, Player, Team)
@@ -420,14 +427,74 @@ class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, Transact
420 427
         }
421 428
     }]"""
422 429
 
423  
-try:
424  
-    import yaml
425  
-except ImportError:
426  
-    pass
427  
-else:
428  
-    class YamlSerializerTestCase(SerializersTestBase, TestCase):
429  
-        serializer_name = "yaml"
430  
-        fwd_ref_str = """- fields:
  430
+
  431
+YAML_IMPORT_ERROR_MESSAGE = r'No module named yaml'
  432
+class YamlImportModuleMock(object):
  433
+    """Provides a wrapped import_module function to simulate yaml ImportError
  434
+
  435
+    In order to run tests that verify the behavior of the YAML serializer
  436
+    when run on a system that has yaml installed (like the django CI server),
  437
+    mock import_module, so that it raises an ImportError when the yaml
  438
+    serializer is being imported.  The importlib.import_module() call is
  439
+    being made in the serializers.register_serializer().
  440
+
  441
+    Refs: #12756
  442
+    """
  443
+    def __init__(self):
  444
+        self._import_module = importlib.import_module
  445
+
  446
+    def import_module(self, module_path):
  447
+        if module_path == serializers.BUILTIN_SERIALIZERS['yaml']:
  448
+            raise ImportError(YAML_IMPORT_ERROR_MESSAGE)
  449
+
  450
+        return self._import_module(module_path)
  451
+
  452
+
  453
+class NoYamlSerializerTestCase(TestCase):
  454
+    """Not having pyyaml installed provides a misleading error
  455
+
  456
+    Refs: #12756
  457
+    """
  458
+    @classmethod
  459
+    def setUpClass(cls):
  460
+        """Removes imported yaml and stubs importlib.import_module"""
  461
+        super(NoYamlSerializerTestCase, cls).setUpClass()
  462
+
  463
+        cls._import_module_mock = YamlImportModuleMock()
  464
+        importlib.import_module = cls._import_module_mock.import_module
  465
+
  466
+        # clear out cached serializers to emulate yaml missing
  467
+        serializers._serializers = {}
  468
+
  469
+    @classmethod
  470
+    def tearDownClass(cls):
  471
+        """Puts yaml back if necessary"""
  472
+        super(NoYamlSerializerTestCase, cls).tearDownClass()
  473
+
  474
+        importlib.import_module = cls._import_module_mock._import_module
  475
+
  476
+        # clear out cached serializers to clean out BadSerializer instances
  477
+        serializers._serializers = {}
  478
+
  479
+    def test_serializer_pyyaml_error_message(self):
  480
+        """Using yaml serializer without pyyaml raises ImportError"""
  481
+        jane = Author(name="Jane")
  482
+        self.assertRaises(ImportError, serializers.serialize, "yaml", [jane])
  483
+
  484
+    def test_deserializer_pyyaml_error_message(self):
  485
+        """Using yaml deserializer without pyyaml raises ImportError"""
  486
+        self.assertRaises(ImportError, serializers.deserialize, "yaml", "")
  487
+
  488
+    def test_dumpdata_pyyaml_error_message(self):
  489
+        """Calling dumpdata produces an error when yaml package missing"""
  490
+        self.assertRaisesRegexp(management.CommandError, YAML_IMPORT_ERROR_MESSAGE,
  491
+                management.call_command, 'dumpdata', format='yaml')
  492
+
  493
+
  494
+@unittest.skipUnless(HAS_YAML, "No yaml library detected")
  495
+class YamlSerializerTestCase(SerializersTestBase, TestCase):
  496
+    serializer_name = "yaml"
  497
+    fwd_ref_str = """- fields:
431 498
     headline: Forward references pose no problem
432 499
     pub_date: 2006-06-16 15:00:00
433 500
     categories: [1]
@@ -443,7 +510,7 @@ class YamlSerializerTestCase(SerializersTestBase, TestCase):
443 510
   pk: 1
444 511
   model: serializers.author"""
445 512
 
446  
-        pkless_str = """- fields:
  513
+    pkless_str = """- fields:
447 514
     name: Reference
448 515
   pk: null
449 516
   model: serializers.category
@@ -451,42 +518,44 @@ class YamlSerializerTestCase(SerializersTestBase, TestCase):
451 518
     name: Non-fiction
452 519
   model: serializers.category"""
453 520
 
454  
-        @staticmethod
455  
-        def _validate_output(serial_str):
456  
-            try:
457  
-                yaml.safe_load(StringIO(serial_str))
458  
-            except Exception:
459  
-                return False
460  
-            else:
461  
-                return True
462  
-
463  
-        @staticmethod
464  
-        def _get_pk_values(serial_str):
465  
-            ret_list = []
466  
-            stream = StringIO(serial_str)
467  
-            for obj_dict in yaml.safe_load(stream):
468  
-                ret_list.append(obj_dict["pk"])
469  
-            return ret_list
470  
-
471  
-        @staticmethod
472  
-        def _get_field_values(serial_str, field_name):
473  
-            ret_list = []
474  
-            stream = StringIO(serial_str)
475  
-            for obj_dict in yaml.safe_load(stream):
476  
-                if "fields" in obj_dict and field_name in obj_dict["fields"]:
477  
-                    field_value = obj_dict["fields"][field_name]
478  
-                    # yaml.safe_load will return non-string objects for some
479  
-                    # of the fields we are interested in, this ensures that
480  
-                    # everything comes back as a string
481  
-                    if isinstance(field_value, six.string_types):
482  
-                        ret_list.append(field_value)
483  
-                    else:
484  
-                        ret_list.append(str(field_value))
485  
-            return ret_list
486  
-
487  
-    class YamlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase):
488  
-        serializer_name = "yaml"
489  
-        fwd_ref_str = """- fields:
  521
+    @staticmethod
  522
+    def _validate_output(serial_str):
  523
+        try:
  524
+            yaml.safe_load(StringIO(serial_str))
  525
+        except Exception:
  526
+            return False
  527
+        else:
  528
+            return True
  529
+
  530
+    @staticmethod
  531
+    def _get_pk_values(serial_str):
  532
+        ret_list = []
  533
+        stream = StringIO(serial_str)
  534
+        for obj_dict in yaml.safe_load(stream):
  535
+            ret_list.append(obj_dict["pk"])
  536
+        return ret_list
  537
+
  538
+    @staticmethod
  539
+    def _get_field_values(serial_str, field_name):
  540
+        ret_list = []
  541
+        stream = StringIO(serial_str)
  542
+        for obj_dict in yaml.safe_load(stream):
  543
+            if "fields" in obj_dict and field_name in obj_dict["fields"]:
  544
+                field_value = obj_dict["fields"][field_name]
  545
+                # yaml.safe_load will return non-string objects for some
  546
+                # of the fields we are interested in, this ensures that
  547
+                # everything comes back as a string
  548
+                if isinstance(field_value, six.string_types):
  549
+                    ret_list.append(field_value)
  550
+                else:
  551
+                    ret_list.append(str(field_value))
  552
+        return ret_list
  553
+
  554
+
  555
+@unittest.skipUnless(HAS_YAML, "No yaml library detected")
  556
+class YamlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase):
  557
+    serializer_name = "yaml"
  558
+    fwd_ref_str = """- fields:
490 559
     headline: Forward references pose no problem
491 560
     pub_date: 2006-06-16 15:00:00
492 561
     categories: [1]
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 3df9647

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