Skip to content
This repository has been archived by the owner on Mar 8, 2021. It is now read-only.

Commit

Permalink
Add the possibility to uninstall fixtures
Browse files Browse the repository at this point in the history
This should be used while tearing down tests. It is complementary to
install_fixtures.
  • Loading branch information
JordanB committed Jan 9, 2014
1 parent 145707f commit 19db9b6
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 27 deletions.
127 changes: 116 additions & 11 deletions charlatan/fixtures_manager.py
Expand Up @@ -39,6 +39,7 @@ class FixturesManager(object):

def __init__(self):
self.hooks = {}
self.installed_keys = []

def load(self, filename, db_session=None, models_package=""):
"""Pre-load the fixtures.
Expand Down Expand Up @@ -118,6 +119,7 @@ def add_to_graph(fixture):
def clean_cache(self):
"""Clean the cache."""
self.cache = {}
self.installed_keys = []

def save_instance(self, instance):
"""Save a fixture instance.
Expand All @@ -144,6 +146,35 @@ def save_instance(self, instance):

self._get_hook("after_save")(instance)

def delete_instance(self, instance):
"""Delete a fixture instance.
If it's a SQLAlchemy model, it will be deleted from the session and the
session will be committed.
Otherwise, :meth:`delete_instance` will be run first. If the instance
does not have it, :meth:`delete` will be run. If the instance does not
have it, nothing will happen.
Before and after the process, the :func:`before_delete` and
:func:`after_delete` hook are run.
"""

self._get_hook("before_delete")(instance)

if self.session and is_sqlalchemy_model(instance):
self.session.delete(instance)
self.session.commit()

else:
try:
getattr(instance, "delete_instance")()
except AttributeError:
getattr(instance, "delete", lambda: None)()

self._get_hook("after_delete")(instance)

def install_fixture(self, fixture_key, do_not_save=False,
include_relationships=True, attrs=None):

Expand Down Expand Up @@ -189,12 +220,8 @@ def install_fixtures(self, fixture_keys, do_not_save=False,
:rtype: list of :data:`fixture_instance`
"""

if isinstance(fixture_keys, basestring):
fixture_keys = (fixture_keys, )

instances = []
for f in fixture_keys:
for f in self.make_list(fixture_keys):
instances.append(self.install_fixture(
f,
do_not_save=do_not_save,
Expand All @@ -218,6 +245,59 @@ def install_all_fixtures(self, do_not_save=False,
do_not_save=do_not_save,
include_relationships=include_relationships)

def uninstall_fixture(self, fixture_key):
"""Uninstall a fixture.
:param str fixture_key:
:rtype: :data:`fixture_instance` or None if no instance was installed
with the given key
"""

try:
self._get_hook("before_uninstall")()
instance = self.cache.get(fixture_key)
if instance:
self.delete_instance(instance)
self.cache.pop(fixture_key, None)
self.installed_keys.remove(fixture_key)

except Exception as exc:
self._get_hook("after_uninstall")(exc)
raise

else:
self._get_hook("after_uninstall")(None)
return instance

def uninstall_fixtures(self, fixture_keys):
"""Uninstall a list of installed fixtures.
If a given fixture was not previously installed, nothing happens and
its instance is not part of the returned list.
:param fixture_keys: fixtures to be uninstalled
:type fixture_keys: str or list of strs
:rtype: list of :data:`fixture_instance`
"""
instances = []
for fixture_key in self.make_list(fixture_keys):
instance = self.uninstall_fixture(fixture_key)
if instance:
instances.append(instance)

return instances

def uninstall_all_fixtures(self):
"""Uninstall all installed fixtures.
:rtype: list of :data:`fixture_instance`
"""
installed_fixture_keys = list(self.installed_keys)
installed_fixture_keys.reverse()
return self.uninstall_fixtures(installed_fixture_keys)

def get_fixture(self, fixture_key, include_relationships=True, attrs=None):
"""Return a fixture instance (but do not save it).
Expand Down Expand Up @@ -260,6 +340,7 @@ def get_fixture(self, fixture_key, include_relationships=True, attrs=None):
)

self.cache[fixture_key] = instance
self.installed_keys.append(fixture_key)

# If any arguments are passed in, set them before returning. But do not
# set them on a list of fixtures (they are already set on all elements)
Expand Down Expand Up @@ -306,6 +387,12 @@ def set_hook(self, hookname, func):

self.hooks[hookname] = func

def make_list(self, obj):
"""Return list of objects if necessary."""
if isinstance(obj, (list, tuple)):
return obj
return (obj, )


FIXTURES_MANAGER = FixturesManager()

Expand Down Expand Up @@ -340,12 +427,7 @@ def install_fixture(self, fixture_key, do_not_save=False,
@copy_docstring_from(FixturesManager)
def install_fixtures(self, fixture_keys, do_not_save=False,
include_relationships=True):

# Let's be forgiving
if isinstance(fixture_keys, basestring):
fixture_keys = (fixture_keys, )

for f in fixture_keys:
for f in FIXTURES_MANAGER.make_list(fixture_keys):
self.install_fixture(f,
do_not_save=do_not_save,
include_relationships=include_relationships)
Expand All @@ -357,6 +439,29 @@ def install_all_fixtures(self, do_not_save=False,
do_not_save=do_not_save,
include_relationships=include_relationships)

@copy_docstring_from(FixturesManager)
def uninstall_fixture(self, fixture_key):
instance = FIXTURES_MANAGER.uninstall_fixture(fixture_key)
if instance:
delattr(self, fixture_key)
return instance

@copy_docstring_from(FixturesManager)
def uninstall_fixtures(self, fixture_keys):
instances = []
for fixture_key in FIXTURES_MANAGER.make_list(fixture_keys):
instance = self.uninstall_fixture(fixture_key)
if instance:
instances.append(instance)

return instances

@copy_docstring_from(FixturesManager)
def uninstall_all_fixtures(self):
installed_keys = list(FIXTURES_MANAGER.installed_keys)
installed_keys.reverse()
return self.uninstall_fixtures(installed_keys)

def clean_fixtures_cache(self):
"""Clean the cache."""
FIXTURES_MANAGER.clean_cache()
43 changes: 33 additions & 10 deletions charlatan/testcase.py
Expand Up @@ -14,7 +14,7 @@ def use_fixtures_manager(self, fixtures_manager):
if hasattr(self, "fixtures"):
self.install_fixtures(self.fixtures)

def install_fixtures(self, fixtures, do_not_save=False):
def install_fixtures(self, fixtures=None, do_not_save=False):
"""Install required fixtures.
:param fixtures: fixtures key
Expand All @@ -25,11 +25,7 @@ def install_fixtures(self, fixtures, do_not_save=False):
"""

if fixtures:
# Be forgiving
if not isinstance(fixtures, (list, tuple)):
fixtures = (fixtures, )
fixtures_to_install = fixtures

fixtures_to_install = self.__fixtures_manager.make_list(fixtures)
else:
fixtures_to_install = self.fixtures

Expand All @@ -50,14 +46,41 @@ def install_fixture(self, fixture_name):

def create_all_fixtures(self):
"""Create all available fixtures but do not save them."""

# Adding fixtures to the class
for f in self.__fixtures_manager.install_all(do_not_save=True):
setattr(self, f[0], f[1])
return self.install_fixtures(do_not_save=True)

def get_fixture(self, fixture_name):
"""Return a fixture instance (but do not save it).
:param str fixture_name: fixture key
"""
return self.__fixtures_manager.get_fixture(fixture_name)

def uninstall_fixtures(self, fixtures=None):
"""Uninstall fixtures.
:param fixtures: fixtures key
:type fixtures: list of strings
If :data:`fixtures` is not provided, the method will look for a class
property named :attr:`fixtures`.
"""

if not fixtures:
# copy and reverse the list in order to remove objects with
# relationships first
fixtures = list(self.__fixtures_manager.installed_keys)
fixtures.reverse()

fixtures = self.__fixtures_manager.make_list(fixtures)
uninstalled = self.__fixtures_manager.uninstall_fixtures(fixtures)

# Removing fixtures from the class
for fixture in fixtures:
delattr(self, fixture)

# Return list of fixture instances
return uninstalled

def uninstall_fixture(self, fixture_name):
"""Uninstall a fixture and return it."""
return self.uninstall_fixtures(fixture_name)[0]
61 changes: 61 additions & 0 deletions charlatan/tests/test_fixtures_manager.py
Expand Up @@ -21,6 +21,67 @@ def test_install_fixture(self):
'field2': 2,
})

def test_uninstall_fixture(self):
"""uninstall_fixture should return the fixture"""

fixtures_manager = FixturesManager()
fixtures_manager.load(
'./charlatan/tests/data/relationships_without_models.yaml')

fixtures_manager.install_fixture('simple_dict')
fixture = fixtures_manager.uninstall_fixture('simple_dict')
self.assertEqual(fixture, {
'field1': 'lolin',
'field2': 2,
})

# verify we are forgiving with list inputs
fixtures = fixtures_manager.install_fixtures('simple_dict')
self.assertEqual(len(fixtures), 1)

fixtures = fixtures_manager.uninstall_fixtures('simple_dict')
self.assertEqual(len(fixtures), 1)
self.assertEqual(fixtures[0], {
'field1': 'lolin',
'field2': 2,
})

def test_uninstall_non_installed_fixture(self):
"""
uninstall_fixture should return None if the fixture has not been
previously installed.
"""

fixtures_manager = FixturesManager()
fixtures_manager.load(
'./charlatan/tests/data/relationships_without_models.yaml')

fixture = fixtures_manager.uninstall_fixture('simple_dict')
self.assertEqual(fixture, None)

def test_uninstall_fixtures(self):
"""
uninstall_fixtures should return the list of previously installed
fixtures that are now uninstalled.
"""
fixtures_manager = FixturesManager()
fixtures_manager.load(
'./charlatan/tests/data/relationships_without_models.yaml')

fixture_keys = ('simple_dict', 'dict_with_nest')

fixtures_manager.install_fixtures(fixture_keys)
self.assertEqual(len(fixtures_manager.cache.keys()), 2)

fixtures = fixtures_manager.uninstall_fixtures(fixture_keys)
self.assertEqual(len(fixtures), 2)
self.assertEqual(len(fixtures_manager.cache.keys()), 0)

# uninstalling non-exiting fixtures should not raise an exception
fixtures = fixtures_manager.uninstall_fixtures(fixture_keys)
self.assertEqual(len(fixtures), 0)
self.assertEqual(len(fixtures_manager.cache.keys()), 0)

def test_dependency_parsing(self):
fm = FixturesManager()
fm.load(
Expand Down

0 comments on commit 19db9b6

Please sign in to comment.