Skip to content

Instantly share code, notes, and snippets.

@seanjtaylor
Created January 11, 2017 18:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seanjtaylor/43e0a43916b4d5a4f33a297111146de2 to your computer and use it in GitHub Desktop.
Save seanjtaylor/43e0a43916b4d5a4f33a297111146de2 to your computer and use it in GitHub Desktop.
Problem with setuptools

In my setup.py build command, I create a pickle file that I'd like to be available at runtime. I do this by subclassing from the setuptools.command.build_py.build_py and overriding the run command:

class my_command(build_py):
    def run(self):
        target_dir = os.path.join(self.build_lib, 'my_package/data')
        self.mkpath(target_dir)
        with open(os.path.join(target_dir, 'my_file.pkl'), 'wb') as f:
            pickle.dump({'foo': 'bar'}, f)

and then including it as a custom class:

setup(
    ...,
    cmdclass={
        'build_py': my_command,
    },
    test_suite='my_package.tests',
)

This works fine, it's available and I'm able to retrieve it at runtime in my package by using:

my_pickle = pkg_resources.resource_filename(
  'my_package',
  'data/my_file.pkl'
)

But when I run tests python setup.py test, they are not executed in the build directory but in the source directory and pkg_resources.resource_filename points to a file that does not exist.

How can I get my tests to run in the build directory, not in the package source directory?

@seanjtaylor
Copy link
Author

Solution is to use this subclass of the test command, which ensures that the tests are run in a build directory. This is already the default for Python 3 when use_2to3 is enabled, I just had to take out the if statement in the subclass.

class TestCommand(test_command):
    """We must run tests on the build directory, not source."""

    def with_project_on_sys_path(self, func):
        # Ensure metadata is up-to-date
        self.reinitialize_command('build_py', inplace=0)
        self.run_command('build_py')
        bpy_cmd = self.get_finalized_command("build_py")
        build_path = normalize_path(bpy_cmd.build_lib)

        # Build extensions
        self.reinitialize_command('egg_info', egg_base=build_path)
        self.run_command('egg_info')

        self.reinitialize_command('build_ext', inplace=0)
        self.run_command('build_ext')

        ei_cmd = self.get_finalized_command("egg_info")

        old_path = sys.path[:]
        old_modules = sys.modules.copy()

        try:
            sys.path.insert(0, normalize_path(ei_cmd.egg_base))
            working_set.__init__()
            add_activation_listener(lambda dist: dist.activate())
            require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
            func()
        finally:
            sys.path[:] = old_path
            sys.modules.clear()
            sys.modules.update(old_modules)
            working_set.__init__()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment