Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

adds different scopes for datadirs #27

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
pytest-datadir
==============

1.4.0 (2019-04-30)
------------------

- Implements the ``DatadirFactory`` class which exposes the ``mkdatadir`` method that generates datadir fixtures based on the parameters of a scope and the relative path of the datadir which is intended to be copied to the temporary data directory. This allows for datadirs for different scopes other than function. (`#26 <https://github.com/gabrielcnr/pytest-datadir/issues/26>`_).

1.3.0 (2019-01-15)
------------------

Expand Down
162 changes: 146 additions & 16 deletions pytest_datadir/plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import shutil
import sys
from copy import copy

if sys.version_info[0] == 2:
from pathlib2 import Path
Expand All @@ -9,6 +10,10 @@

import pytest

# the datadir factory uses a tmp_path_factory to get a temp dir. This
# is the name of the dir within the tempdir tree to use for datadir,
# since these are potentially session scoped fixtures
DATADIR_DIRNAME = 'datadir'

def _win32_longpath(path):
'''
Expand All @@ -25,24 +30,149 @@ def _win32_longpath(path):
return path


@pytest.fixture
def shared_datadir(request, tmpdir):
original_shared_path = os.path.join(request.fspath.dirname, 'data')
temp_path = Path(str(tmpdir.join('data')))
shutil.copytree(_win32_longpath(original_shared_path), _win32_longpath(str(temp_path)))
return temp_path


@pytest.fixture
def original_datadir(request):
return Path(os.path.splitext(request.module.__file__)[0])


@pytest.fixture
def datadir(original_datadir, tmpdir):
result = Path(str(tmpdir.join(original_datadir.stem)))
if original_datadir.is_dir():
shutil.copytree(_win32_longpath(str(original_datadir)), _win32_longpath(str(result)))
else:
result.mkdir()
return result

class DatadirFactory(object):
"""Factory class for generating datadir fixtures."""

def __init__(self, request, tmp_path_factory):

self.tmp_path_factory = tmp_path_factory
self.request = request

def mkdatadir(self, original_datadir='data'):
salotz marked this conversation as resolved.
Show resolved Hide resolved

# special condition if the datadir is specified as None, which
# automatically gets the path that matches the basename of the
# module we are in
if original_datadir is None:
original_datadir = Path(os.path.splitext(self.request.module.__file__)[0])


# get the path to the shared data dir
original_path = Path(self.request.fspath.dirname) / original_datadir

# make sure that the path exists and it is a directory
exists = True
if not original_path.exists():
# raise the flag that it doesn't exist so we can generate
# a directory for it instead of copying
exists = False

# make sure the path is a directory if it exists
elif not original_path.is_dir():
raise ValueError("datadir path is not a directory")

# generate a base temporary directory and receive the path to it
temp_path = self.tmp_path_factory.mktemp('')

# in order to use the shutil.copytree util the target directory
# must not exist so we specify a dir in the generated tempdir for it
temp_data_path = temp_path / DATADIR_DIRNAME

# windows-ify the paths
original_path = Path(_win32_longpath(original_path))
temp_data_path = Path(_win32_longpath(str(temp_data_path)))

# copy or create empty directory depending on whether the
# original one exists
if exists:

# copy all the files in the original data dir to the temp
# dir
shutil.copytree(original_path, temp_data_path)

else:
# otherwise just give them a fallback tmpdir
temp_data_path.mkdir()

return temp_data_path

# to clean up for the different scopes we need tmp_path_factory to be
# scoped differently; just copy the code from module and change the
# decorator

@pytest.fixture(scope="session")
def session_tmp_path_factory(request):
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
"""
return request.config._tmp_path_factory

@pytest.fixture(scope="module")
def module_tmp_path_factory(request):
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
"""
return request.config._tmp_path_factory


@pytest.fixture(scope="class")
def class_tmp_path_factory(request):
salotz marked this conversation as resolved.
Show resolved Hide resolved
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
"""
return request.config._tmp_path_factory

@pytest.fixture(scope="function")
def function_tmp_path_factory(request):
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
"""
return request.config._tmp_path_factory

# scoped factories
@pytest.fixture(scope='module')
def module_datadir_factory(request, module_tmp_path_factory):
salotz marked this conversation as resolved.
Show resolved Hide resolved

return DatadirFactory(request, module_tmp_path_factory)

@pytest.fixture(scope='class')
def class_datadir_factory(request, class_tmp_path_factory):

return DatadirFactory(request, class_tmp_path_factory)

@pytest.fixture(scope='function')
def function_datadir_factory(request, function_tmp_path_factory):

return DatadirFactory(request, function_tmp_path_factory)


# shared datadirs

@pytest.fixture(scope='module')
def module_shared_datadir(request, module_datadir_factory):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure having specific "shared_datadir" fixtures for each scope makes sense. Users could just use datadir_factory directly, similar to how pytest does it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree here. Requesting new datadirs (mkdatadir()) from the factory involves copying which we are trying to avoid.


return module_datadir_factory.mkdatadir()

@pytest.fixture(scope='class')
def class_shared_datadir(request, class_datadir_factory):

return class_datadir_factory.mkdatadir()

@pytest.fixture(scope='function')
def function_shared_datadir(request, function_datadir_factory):

return function_datadir_factory.mkdatadir()

shared_datadir = function_shared_datadir


# test module datadirs

@pytest.fixture(scope='module')
def module_datadir(request, module_datadir_factory):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here about having multiple scopes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto disagreement above


return module_datadir_factory.mkdatadir(original_datadir=None)

@pytest.fixture(scope='class')
def class_datadir(request, class_datadir_factory):

return class_datadir_factory.mkdatadir(original_datadir=None)

@pytest.fixture(scope='function')
def function_datadir(request, function_datadir_factory):

return function_datadir_factory.mkdatadir(original_datadir=None)

datadir = function_datadir