aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaphaël Barrois <raphael.barrois@polytechnique.org>2013-09-17 00:28:48 +0200
committerRaphaël Barrois <raphael.barrois@polytechnique.org>2013-09-17 00:28:54 +0200
commita8742c973db224968b74bb054027130b2ab458e0 (patch)
treee6aff374e0a4fc5f9bc9937c435bec08454f9279
parentda715e33bbba0428f0be25e8cc3ff4e88ec72bbb (diff)
downloadfactory-boy-a8742c973db224968b74bb054027130b2ab458e0.tar
factory-boy-a8742c973db224968b74bb054027130b2ab458e0.tar.gz
Add 'factory.debug' context manager.
-rw-r--r--README8
-rw-r--r--docs/changelog.rst1
-rw-r--r--docs/orms.rst4
-rw-r--r--docs/reference.rst32
-rw-r--r--factory/__init__.py2
-rw-r--r--factory/helpers.py18
-rw-r--r--tests/__init__.py1
-rw-r--r--tests/compat.py5
-rw-r--r--tests/test_helpers.py76
9 files changed, 144 insertions, 3 deletions
diff --git a/README b/README
index bc5b55f..0371b28 100644
--- a/README
+++ b/README
@@ -188,10 +188,16 @@ Debugging factory_boy
"""""""""""""""""""""
Debugging factory_boy can be rather complex due to the long chains of calls.
-Detailed logging is available through the ``factory`` logger:
+Detailed logging is available through the ``factory`` logger.
+
+A helper, :meth:`factory.debug()`, is available to ease debugging:
.. code-block:: python
+ with factory.debug():
+ obj = TestModel2Factory()
+
+
import logging
logger = logging.getLogger('factory')
logger.addHandler(logging.StreamHandler())
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 0367246..326b245 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -15,6 +15,7 @@ ChangeLog
- The :class:`~factory.django.DjangoModelFactory` now supports the ``FACTORY_FOR = 'myapp.MyModel'``
syntax, making it easier to shove all factories in a single module (:issue:`66`).
+ - Add :meth:`factory.debug()` helper for easier backtrace analysis
- Adding factory support for mongoengine with :class:`~factory.mongoengine.MongoEngineFactory`.
.. _v2.1.2:
diff --git a/docs/orms.rst b/docs/orms.rst
index 74c5c62..a463bfb 100644
--- a/docs/orms.rst
+++ b/docs/orms.rst
@@ -165,7 +165,7 @@ factory_boy supports `Mogo`_-style models, through the :class:`MogoFactory` clas
MongoEngine
-----
+-----------
.. currentmodule:: factory.mongoengine
@@ -173,7 +173,7 @@ factory_boy supports `MongoEngine`_-style models, through the :class:`MongoEngin
`mongoengine`_ is a wrapper around the ``pymongo`` library for MongoDB.
-.. _mongoengine:: http://mongoengine.org/
+.. _mongoengine: http://mongoengine.org/
.. class:: MongoEngineFactory(factory.Factory)
diff --git a/docs/reference.rst b/docs/reference.rst
index 3d3097d..53584a0 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -363,6 +363,38 @@ factory_boy supports two main strategies for generating instances, plus stubs.
with a default strategy set to :data:`STUB_STRATEGY`.
+.. function:: debug(logger='factory', stream=None)
+
+ :param str logger: The name of the logger to enable debug for
+ :param file stream: The stream to send debug output to, defaults to :obj:`sys.stderr`
+
+ Context manager to help debugging factory_boy behavior.
+ It will temporarily put the target logger (e.g ``'factory'``) in debug mode,
+ sending all output to :obj`~sys.stderr`;
+ upon leaving the context, the logging levels are reset.
+
+ A typical use case is to understand what happens during a single factory call:
+
+ .. code-block:: python
+
+ with factory.debug():
+ obj = TestModel2Factory()
+
+ This will yield messages similar to those (artificial indentation):
+
+ .. code-block:: ini
+
+ BaseFactory: Preparing tests.test_using.TestModel2Factory(extra={})
+ LazyStub: Computing values for tests.test_using.TestModel2Factory(two=<OrderedDeclarationWrapper for <factory.declarations.SubFactory object at 0x1e15610>>)
+ SubFactory: Instantiating tests.test_using.TestModelFactory(__containers=(<LazyStub for tests.test_using.TestModel2Factory>,), one=4), create=True
+ BaseFactory: Preparing tests.test_using.TestModelFactory(extra={'__containers': (<LazyStub for tests.test_using.TestModel2Factory>,), 'one': 4})
+ LazyStub: Computing values for tests.test_using.TestModelFactory(one=4)
+ LazyStub: Computed values, got tests.test_using.TestModelFactory(one=4)
+ BaseFactory: Generating tests.test_using.TestModelFactory(one=4)
+ LazyStub: Computed values, got tests.test_using.TestModel2Factory(two=<tests.test_using.TestModel object at 0x1e15410>)
+ BaseFactory: Generating tests.test_using.TestModel2Factory(two=<tests.test_using.TestModel object at 0x1e15410>)
+
+
.. _declarations:
Declarations
diff --git a/factory/__init__.py b/factory/__init__.py
index a27eb40..a71fea5 100644
--- a/factory/__init__.py
+++ b/factory/__init__.py
@@ -58,6 +58,8 @@ from .declarations import (
)
from .helpers import (
+ debug,
+
build,
create,
stub,
diff --git a/factory/helpers.py b/factory/helpers.py
index 8f0d161..37b41bf 100644
--- a/factory/helpers.py
+++ b/factory/helpers.py
@@ -23,11 +23,29 @@
"""Simple wrappers around Factory class definition."""
+import contextlib
+import logging
from . import base
from . import declarations
+@contextlib.contextmanager
+def debug(logger='factory', stream=None):
+ logger_obj = logging.getLogger(logger)
+ old_level = logger_obj.level
+
+ handler = logging.StreamHandler(stream)
+ handler.setLevel(logging.DEBUG)
+ logger_obj.addHandler(handler)
+ logger_obj.setLevel(logging.DEBUG)
+
+ yield
+
+ logger_obj.setLevel(old_level)
+ logger_obj.removeHandler(handler)
+
+
def make_factory(klass, **kwargs):
"""Create a new, simple factory for the given class."""
factory_name = '%sFactory' % klass.__name__
diff --git a/tests/__init__.py b/tests/__init__.py
index d823a87..5b6fc55 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -6,6 +6,7 @@ from .test_containers import *
from .test_declarations import *
from .test_django import *
from .test_fuzzy import *
+from .test_helpers import *
from .test_using import *
from .test_utils import *
from .test_alchemy import *
diff --git a/tests/compat.py b/tests/compat.py
index f11076c..ff96f13 100644
--- a/tests/compat.py
+++ b/tests/compat.py
@@ -30,6 +30,11 @@ if sys.version_info[0:2] < (2, 7): # pragma: no cover
else: # pragma: no cover
import unittest
+if sys.version_info[0] == 2: # pragma: no cover
+ import StringIO as io
+else: # pragma: no cover
+ import io
+
if sys.version_info[0:2] < (3, 3): # pragma: no cover
import mock
else: # pragma: no cover
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
new file mode 100644
index 0000000..f5a66e5
--- /dev/null
+++ b/tests/test_helpers.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2010 Mark Sandstrom
+# Copyright (c) 2011-2013 Raphaël Barrois
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import logging
+
+from factory import helpers
+
+from .compat import io, unittest
+
+
+class DebugTest(unittest.TestCase):
+ """Tests for the 'factory.debug()' helper."""
+
+ def test_default_logger(self):
+ stream1 = io.StringIO()
+ stream2 = io.StringIO()
+
+ l = logging.getLogger('factory.test')
+ h = logging.StreamHandler(stream1)
+ h.setLevel(logging.INFO)
+ l.addHandler(h)
+
+ # Non-debug: no text gets out
+ l.debug("Test")
+ self.assertEqual('', stream1.getvalue())
+
+ with helpers.debug(stream=stream2):
+ # Debug: text goes to new stream only
+ l.debug("Test2")
+
+ self.assertEqual('', stream1.getvalue())
+ self.assertEqual("Test2\n", stream2.getvalue())
+
+ def test_alternate_logger(self):
+ stream1 = io.StringIO()
+ stream2 = io.StringIO()
+
+ l1 = logging.getLogger('factory.test')
+ l2 = logging.getLogger('factory.foo')
+ h = logging.StreamHandler(stream1)
+ h.setLevel(logging.DEBUG)
+ l2.addHandler(h)
+
+ # Non-debug: no text gets out
+ l1.debug("Test")
+ self.assertEqual('', stream1.getvalue())
+ l2.debug("Test")
+ self.assertEqual('', stream1.getvalue())
+
+ with helpers.debug('factory.test', stream=stream2):
+ # Debug: text goes to new stream only
+ l1.debug("Test2")
+ l2.debug("Test3")
+
+ self.assertEqual("", stream1.getvalue())
+ self.assertEqual("Test2\n", stream2.getvalue())
+