pax_global_header00006660000000000000000000000064141445204740014517gustar00rootroot0000000000000052 comment=6f0032414a7b3fa75070bef2fe6b95eb76053d9f jupyter-packaging-0.11.1/000077500000000000000000000000001414452047400152235ustar00rootroot00000000000000jupyter-packaging-0.11.1/.github/000077500000000000000000000000001414452047400165635ustar00rootroot00000000000000jupyter-packaging-0.11.1/.github/workflows/000077500000000000000000000000001414452047400206205ustar00rootroot00000000000000jupyter-packaging-0.11.1/.github/workflows/main.yml000066400000000000000000000016031414452047400222670ustar00rootroot00000000000000name: CI on: push: branches: '*' pull_request: branches: '*' schedule: - cron: '0 0 * * *' jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python_version: ["3.7", "3.9", "3.10"] os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Checkout uses: actions/checkout@v2 - name: Install Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python_version }} architecture: 'x64' - name: Install dependencies run: | python -m pip install -U pip codecov pip install -e .[test] - name: Run test run: | python setup.py --version python -m build . pytest -vv --cov jupyter_packaging --cov-branch --cov-report term-missing:skip-covered - name: Coverage run: | codecov jupyter-packaging-0.11.1/.gitignore000066400000000000000000000020251414452047400172120ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # IPython Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # dotenv .env # virtualenv venv/ ENV/ # Spyder project settings .spyderproject # Rope project settings .ropeproject jupyter-packaging-0.11.1/CHANGELOG.md000066400000000000000000000217361414452047400170450ustar00rootroot00000000000000# Changelog ## 0.11.1 - Fix running testsuite within virtualenv [#111](https://github.com/jupyter/jupyter-packaging/pull/111) ([@jnahmias](https://github.com/jnahmias)) ## 0.11.0 - Drop support for Python 3.6 and add support for Python 3.10. [#109](https://github.com/jupyter/jupyter-packaging/pull/109) ([@blink1073](https://github.com/blink1073)) ## 0.10.6 - The import of `bdist_wheel` is optional, must check for `None` before using it [#106](https://github.com/jupyter/jupyter-packaging/pull/106) ([@ellert](https://github.com/ellert)) ## 0.10.5 - Fix last one hardcoded unversioned python command [#98](https://github.com/jupyter/jupyter-packaging/pull/98) ([@frenzymadness](https://github.com/frenzymadness)) - Add note about using the build package [#104](https://github.com/jupyter/jupyter-packaging/pull/104) ([@blink1073](https://github.com/blink1073)) ## 0.10.4 - Handle missing yarn [#99](https://github.com/jupyter/jupyter-packaging/pull/99) ([@blink1073](https://github.com/blink1073)) ## 0.10.3 - Some fixes for issues discovered during packaging [#96](https://github.com/jupyter/jupyter-packaging/pull/96) ([@frenzymadness](https://github.com/frenzymadness)) - Disallow deprecated function return incorrect results for Python 3.10 [#97](https://github.com/jupyter/jupyter-packaging/pull/97) ([@frenzymadness](https://github.com/frenzymadness)) - Fix handling of module metadata in tests [#92](https://github.com/jupyter/jupyter-packaging/pull/92) ([@blink1073](https://github.com/blink1073)) ## 0.10.2 - Fix Handling of npm Parameter [#90](https://github.com/jupyter/jupyter-packaging/pull/90) ([@jtpio](https://github.com/jtpio)) ## 0.10.1 - Fix Handling of Skip If Exists [#86](https://github.com/jupyter/jupyter-packaging/pull/86) ([@jtpio](https://github.com/jtpio)) ## 0.10.0 - Add more options to Build [#84](https://github.com/jupyter/jupyter-packaging/pull/84) ([@jtpio](https://github.com/jtpio)) ## 0.9.2 * Clean up handling of version info [#82](https://github.com/jupyter/jupyter-packaging/pull/82) ([@jtpio](https://github.com/jtpio)) ## 0.9.1 * Do not run ensure_targets in develop mode [#79](https://github.com/jupyter/jupyter-packaging/pull/79) ([@jtpio](https://github.com/jtpio)) ## 0.9.0 * Add ability to ensure targets [#77](https://github.com/jupyter/jupyter-packaging/pull/77) ([@jtpio](https://github.com/jtpio)) * Add version info helper function [#76](https://github.com/jupyter/jupyter-packaging/pull/76) ([@afshin](https://github.com/afshin)) ## 0.8.3 * Fixes handling of backend [#75](https://github.com/jupyter/jupyter-packaging/pull/75) ([@jtpio](https://github.com/jtpio)) ## 0.8.2 * Fix invalid command build [#72](https://github.com/jupyter/jupyter-packaging/pull/72) ([@xmnlab](https://github.com/xmnlab)) ## 0.8.1 * Fix Usage of install_npm [#71](https://github.com/jupyter/jupyter-packaging/pull/71) ([@afshin](https://github.com/afshin)) ## 0.8.0 * Proposal: Improved integration with setuptools [#69](https://github.com/jupyter/jupyter-packaging/pull/69) ([@afshin](https://github.com/afshin)) * Update changelog [#68](https://github.com/jupyter/jupyter-packaging/pull/68) ([@blink1073](https://github.com/blink1073)) ## 0.7.12 * Use sdist from setuptools not distutils [#66](https://github.com/jupyter/jupyter-packaging/pull/66) ([@astrofrog](https://github.com/astrofrog)) ## 0.7.11 * Fix packagedata docstring examples [#62](https://github.com/jupyter/jupyter-packaging/pull/62) ([@vidartf](https://github.com/vidartf)) ## 0.7.10 * Only run build command if one is given [#61](https://github.com/jupyter/jupyter-packaging/pull/61) ([@vidartf](https://github.com/vidartf)) * Add basic test for skip_if_exists [#60](https://github.com/jupyter/jupyter-packaging/pull/60) ([@jtpio](https://github.com/jtpio)) ## 0.7.9 * Fix typo in skip_if_exists [#59](https://github.com/jupyter/jupyter-packaging/pull/59) ([@jtpio](https://github.com/jtpio)) ## 0.7.8 * Fix skip_if_exists logic [#58](https://github.com/jupyter/jupyter-packaging/pull/58) ([@jtpio](https://github.com/jtpio)) * test for nested source folder [#57](https://github.com/jupyter/jupyter-packaging/pull/57) ([@vidartf](https://github.com/vidartf)) ## 0.7.7 * allow trailing slashes in spec [#56](https://github.com/jupyter/jupyter-packaging/pull/56) ([@vidartf](https://github.com/vidartf)) ## 0.7.6 * Add local testing instructions [#55](https://github.com/jupyter/jupyter-packaging/pull/55) ([@blink1073](https://github.com/blink1073)) * feat: add skip_if_exists command to skip when paths exists [#54](https://github.com/jupyter/jupyter-packaging/pull/54) ([@maartenbreddels](https://github.com/maartenbreddels)) * Allow --prefix to work [#52](https://github.com/jupyter/jupyter-packaging/pull/52) ([@dsblank](https://github.com/dsblank)) ## 0.7.4 * Import sdist from distutils instead of setuptools [#51](https://github.com/jupyter/jupyter-packaging/pull/51) ([@jasongrout](https://github.com/jasongrout)) ## 0.7.2 * Require the packaging package [#49](https://github.com/jupyter/jupyter-packaging/pull/49) ([@blink1073](https://github.com/blink1073)) * Switch to gh actions [#48](https://github.com/jupyter/jupyter-packaging/pull/48) ([@blink1073](https://github.com/blink1073)) ## 0.7.1 * Allow files to be excluded [#47](https://github.com/jupyter/jupyter-packaging/pull/47) ([@blink1073](https://github.com/blink1073)) ## 0.7.0 * Test using pyproject.toml and modernize [#46](https://github.com/jupyter/jupyter-packaging/pull/46) ([@blink1073](https://github.com/blink1073)) ## 0.6.1 * Remove brittle check for whether to run npm/yarn install [#44](https://github.com/jupyter/jupyter-packaging/pull/44) ([@blink1073](https://github.com/blink1073)) ## 0.6.0 * move data files to the correct place on develop install [#41](https://github.com/jupyter/jupyter-packaging/pull/41) ([@Zsailer](https://github.com/Zsailer)) ## 0.5.0 * Add changelog and update example [#37](https://github.com/jupyter/jupyter-packaging/pull/37) ([@blink1073](https://github.com/blink1073)) * Update readme to mention pep 518 [#36](https://github.com/jupyter/jupyter-packaging/pull/36) ([@blink1073](https://github.com/blink1073)) * Add handling of data_files in develop mode and add test [#35](https://github.com/jupyter/jupyter-packaging/pull/35) ([@blink1073](https://github.com/blink1073)) * do not pass absolute path to which [#33](https://github.com/jupyter/jupyter-packaging/pull/33) ([@MeggyCal](https://github.com/MeggyCal)) * which finds python executable [#32](https://github.com/jupyter/jupyter-packaging/pull/32) ([@MeggyCal](https://github.com/MeggyCal)) ## 0.4.0 * Remove HERE [#30](https://github.com/jupyter/jupyter-packaging/pull/30) ([@vidartf](https://github.com/vidartf)) ## 0.3.0 * Cleanup pt 2 [#29](https://github.com/jupyter/jupyter-packaging/pull/29) ([@vidartf](https://github.com/vidartf)) * Clean up [#28](https://github.com/jupyter/jupyter-packaging/pull/28) ([@vidartf](https://github.com/vidartf)) * Additional error checking in run() [#27](https://github.com/jupyter/jupyter-packaging/pull/27) ([@jmsdnns](https://github.com/jmsdnns)) * Add appveyor test dependencies [#26](https://github.com/jupyter/jupyter-packaging/pull/26) ([@vidartf](https://github.com/vidartf)) * Data spec path issues [#25](https://github.com/jupyter/jupyter-packaging/pull/25) ([@vidartf](https://github.com/vidartf)) * Fixed a broken test in test_find_packages [#22](https://github.com/jupyter/jupyter-packaging/pull/22) ([@jmsdnns](https://github.com/jmsdnns)) * Fix handling of pip install [#21](https://github.com/jupyter/jupyter-packaging/pull/21) ([@blink1073](https://github.com/blink1073)) * restore setuptools import [#20](https://github.com/jupyter/jupyter-packaging/pull/20) ([@minrk](https://github.com/minrk)) * Updates from using this as the JupyterLab basis [#19](https://github.com/jupyter/jupyter-packaging/pull/19) ([@blink1073](https://github.com/blink1073)) * Fix handling of data files [#18](https://github.com/jupyter/jupyter-packaging/pull/18) ([@blink1073](https://github.com/blink1073)) * Append data files instead of overwrite them [#17](https://github.com/jupyter/jupyter-packaging/pull/17) ([@jasongrout](https://github.com/jasongrout)) * Ensure targets [#16](https://github.com/jupyter/jupyter-packaging/pull/16) ([@vidartf](https://github.com/vidartf)) * Add option to force npm install/build [#15](https://github.com/jupyter/jupyter-packaging/pull/15) ([@vidartf](https://github.com/vidartf)) * Add basic tests + fixes for find_packages [#14](https://github.com/jupyter/jupyter-packaging/pull/14) ([@vidartf](https://github.com/vidartf)) * Fix `is_stale` for file paths [#13](https://github.com/jupyter/jupyter-packaging/pull/13) ([@vidartf](https://github.com/vidartf)) * Update package data after wrapped commands [#12](https://github.com/jupyter/jupyter-packaging/pull/12) ([@vidartf](https://github.com/vidartf)) * Adjust is_stale to use recursive mtimes [#10](https://github.com/jupyter/jupyter-packaging/pull/10) ([@vidartf](https://github.com/vidartf)) * Add `main` module [#8](https://github.com/jupyter/jupyter-packaging/pull/8) ([@vidartf](https://github.com/vidartf)) jupyter-packaging-0.11.1/LICENSE000066400000000000000000000027531414452047400162370ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2017, Project Jupyter All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jupyter-packaging-0.11.1/MANIFEST.in000066400000000000000000000000351414452047400167570ustar00rootroot00000000000000include LICENSE graft tests jupyter-packaging-0.11.1/README.md000066400000000000000000000110321414452047400164770ustar00rootroot00000000000000# Jupyter Packaging Tools to help build and install Jupyter Python packages that require a pre-build step that may include JavaScript build steps. ## Install `pip install jupyter-packaging` ## Usage There are three ways to use `jupyter-packaging` in another package. In general, you should not depend on `jupyter_packaging` as a runtime dependency, only as a build dependency. ### As a Build Requirement Use a `pyproject.toml` file as outlined in [pep-518](https://www.python.org/dev/peps/pep-0518/). An example: ```toml [build-system] requires = ["jupyter_packaging~=0.10.0,<2"] build-backend = "setuptools.build_meta" ``` Below is an example `setup.py` using the above config. It assumes the rest of your metadata is in [`setup.cfg`](https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html). We wrap the import in a try/catch to allow the file to be run without `jupyter_packaging` so that `python setup.py` can be run directly when not building. ```py from setuptools import setup try: from jupyter_packaging import wrap_installers, npm_builder builder = npm_builder() cmdclass = wrap_installers(pre_develop=builder, pre_dist=builder) except ImportError: cmdclass = {} setup(cmdclass=cmdclass)) ``` ### As a Build Backend Use the `jupyter_packaging` build backend. The pre-build command is specified as metadata in `pyproject.toml`: ```toml [build-system] requires = ["jupyter_packaging~=0.10.0,<2"] build-backend = "jupyter_packaging.build_api" [tool.jupyter-packaging.builder] factory = "jupyter_packaging.npm_builder" [tool.jupyter-packaging.build-args] build_cmd = "build:src" ``` The corresponding `setup.py` would be greatly simplified: ```py from setuptools import setup setup() ``` The `tool.jupyter-packaging.builder` section expects a `func` value that points to an importable module and a function with dot separators. If not given, no pre-build function will run. The optional `tool.jupyter-packaging.build-args` sections accepts a dict of keyword arguments to give to the pre-build command. The build backend does not handle the `develop` command (`pip install -e .`). If desired, you can wrap just that command: ```py import setuptools try: from jupyter_packaging import wrap_installers, npm_builder builder = npm_builder(build_cmd="build:dev") cmdclass = wrap_installers(pre_develop=builder) except ImportError: cmdclass = {} setup(cmdclass=cmdclass)) ``` The optional `tool.jupyter-packaging.options` section accepts the following options: - `skip-if-exists`: A list of local files whose presence causes the prebuild to skip - `ensured-targets`: A list of local file paths that should exist when the dist commands are run ### As a Vendored File Vendor `setupbase.py` locally alongside `setup.py` and import the module directly. ```py import setuptools from setupbase import wrap_installers, npm_builder func = npm_builder() cmdclass = wrap_installers(post_develop=func, pre_dist=func) setup(cmdclass=cmdclass) ``` ## Usage Notes - This package does not work with the deprecated `python setup.py bdist_wheel` or `python setup.py sdist` commands, PyPA recommends using the [build](https://pypa-build.readthedocs.io/en/latest/index.html) package (`pip install build && python -m build .`). - We recommend using `include_package_data=True` and `MANIFEST.in` to control the assets included in the [package](https://setuptools.readthedocs.io/en/latest/userguide/datafiles.html). - Tools like [`check-manifest`](https://github.com/mgedmin/check-manifest) or [`manifix`](https://github.com/vidartf/manifix) can be used to ensure the desired assets are included. - Simple uses of `data_files` can be handled in `setup.cfg` or in `setup.py`. If recursive directories are needed use `get_data_files()` from this package. - Unfortunately `data_files` are not supported in `develop` mode (a limitation of `setuptools`). You can work around it by doing a full install (`pip install .`) before the develop install (`pip install -e .`), or by adding a script to push the data files to `sys.base_prefix`. ## Development Install ```bash git clone https://github.com/jupyter/jupyter-packaging.git cd jupyter-packaging pip install -e . ``` You can test changes locally by creating a `pyproject.toml` with the following, replacing the local path to the git checkout: ```toml [build-system] requires = ["jupyter_packaging@file://"] build-backend = "setuptools.build_meta" ``` Note: you need to run `pip cache remove jupyter_packaging` any time changes are made to prevent `pip` from using a cached version of the source. jupyter-packaging-0.11.1/jupyter_packaging/000077500000000000000000000000001414452047400207315ustar00rootroot00000000000000jupyter-packaging-0.11.1/jupyter_packaging/__init__.py000066400000000000000000000002201414452047400230340ustar00rootroot00000000000000# coding: utf-8 # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from .setupbase import * jupyter-packaging-0.11.1/jupyter_packaging/__main__.py000066400000000000000000000017231414452047400230260ustar00rootroot00000000000000# coding: utf-8 # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """ Copies jupyter-packaging's setupbase.py to the specified directory. If no directory is given, it uses the current directory. """ import argparse import os import shutil def check_dir(dirpath): if not os.path.isdir(dirpath): raise argparse.ArgumentTypeError( 'Given path is not a directory: %s' % dirpath) return os.path.abspath(dirpath) def main(args=None): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( 'destination', type=check_dir, default='.', nargs='?', help="The directory to copy setupbase.py to.", ) args = parser.parse_args(args) here = os.path.dirname(__file__) source = os.path.join(here, 'setupbase.py') destination = args.destination shutil.copy(source, destination) if __name__ == '__main__': # pragma: no cover main() jupyter-packaging-0.11.1/jupyter_packaging/build_api.py000066400000000000000000000065371414452047400232460ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import importlib from pathlib import Path import os import sys from deprecation import deprecated from setuptools.build_meta import ( get_requires_for_build_wheel, get_requires_for_build_sdist, prepare_metadata_for_build_wheel, build_sdist as orig_build_sdist, build_wheel as orig_build_wheel, ) import tomlkit from jupyter_packaging.setupbase import __version__ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): """Build a wheel with an optional pre-build step.""" builder = _get_build_func() if builder: builder() val = orig_build_wheel(wheel_directory, config_settings=config_settings, metadata_directory=metadata_directory) _ensure_targets() return val def build_sdist(sdist_directory, config_settings=None): """Build an sdist with an optional pre-build step.""" builder = _get_build_func() if builder: builder() val = orig_build_sdist(sdist_directory, config_settings=config_settings) _ensure_targets() return val @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Use `factory =` instead") def _handle_deprecated_metadata(): pass def _get_build_func(): pyproject = Path('pyproject.toml') if not pyproject.exists(): return data = tomlkit.loads(pyproject.read_text(encoding='utf-8')) if 'tool' not in data: return if 'jupyter-packaging' not in data['tool']: return if 'builder' not in data['tool']['jupyter-packaging']: return section = data['tool']['jupyter-packaging'] # Handle deprecated "func" builder kwarg if 'func' in section['builder']: _handle_deprecated_metadata() if not 'factory' in section['builder']: section['builder']['factory'] = section['builder']['func'] if 'factory' not in section['builder']: raise ValueError('Missing `factory` specifier for builder') factory_data = section['builder']['factory'] mod_name, _, factory_name = factory_data.rpartition('.') if 'options' in section and 'skip-if-exists' in section['options']: skip_if_exists = section['options']['skip-if-exists'] if all(Path(path).exists() for path in skip_if_exists): return None # If the module fails to import, try importing as a local script try: mod = importlib.import_module(mod_name) except ImportError: try: sys.path.insert(0, os.getcwd()) mod = importlib.import_module(mod_name) finally: sys.path.pop(0) factory = getattr(mod, factory_name) kwargs = section.get('build-args', {}) return factory(**kwargs) def _ensure_targets(): pyproject = Path('pyproject.toml') if not pyproject.exists(): return data = tomlkit.loads(pyproject.read_text(encoding='utf-8')) if 'tool' not in data: return if 'jupyter-packaging' not in data['tool']: return section = data['tool']['jupyter-packaging'] if 'options' in section and 'ensured-targets' in section['options']: targets = section['options']['ensured-targets'] missing = [t for t in targets if not os.path.exists(t)] if missing: raise ValueError(('missing files: %s' % missing)) jupyter-packaging-0.11.1/jupyter_packaging/setupbase.py000066400000000000000000000733231414452047400233060ustar00rootroot00000000000000# coding: utf-8 # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """ This file originates from the 'jupyter-packaging' package, and contains a set of useful utilities for including npm packages within a Python package. """ from collections import defaultdict from os.path import join as pjoin from pathlib import Path import io import os import functools import pipes import re import shlex from shutil import which import subprocess import sys try: from deprecation import deprecated except ImportError: # shim deprecated to allow setuptools to find the version string in this file deprecated = lambda *args, **kwargs: lambda *args, **kwargs: None # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly # update it when the contents of directories change. if os.path.exists('MANIFEST'): os.remove('MANIFEST') from packaging.version import VERSION_PATTERN from setuptools import Command from setuptools.command.build_py import build_py from setuptools.config import StaticModule # Note: distutils must be imported after setuptools from distutils import log from setuptools.command.sdist import sdist from setuptools.command.develop import develop from setuptools.command.bdist_egg import bdist_egg try: from wheel.bdist_wheel import bdist_wheel except ImportError: # pragma: no cover bdist_wheel = None if sys.platform == 'win32': # pragma: no cover from subprocess import list2cmdline else: def list2cmdline(cmd_list): return ' '.join(map(pipes.quote, cmd_list)) __version__ = '0.11.1' # --------------------------------------------------------------------------- # Top Level Variables # --------------------------------------------------------------------------- SEPARATORS = os.sep if os.altsep is None else os.sep + os.altsep VERSION_REGEX = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) if "--skip-npm" in sys.argv: print("Skipping npm install as requested.") skip_npm = True sys.argv.remove("--skip-npm") else: skip_npm = False # --------------------------------------------------------------------------- # Core Functions # --------------------------------------------------------------------------- def wrap_installers(pre_develop=None, pre_dist=None, post_develop=None, post_dist=None, ensured_targets=None, skip_if_exists=None): """Make a setuptools cmdclass that calls a prebuild function before installing. Parameters ---------- pre_develop: function The function to call prior to the develop command. pre_dist: function The function to call prior to the sdist and wheel commands post_develop: function The function to call after the develop command. post_dist: function The function to call after the sdist and wheel commands. ensured_targets: list A list of local file paths that should exist when the dist commands are run skip_if_exists: list A list of local files whose presence causes the prebuild to skip Notes ----- For any function given, creates a new `setuptools` command that can be run separately, e.g. `python setup.py pre_develop`. Returns ------- A cmdclass dictionary for setup args. """ cmdclass = {} def _make_command(name, func): class _Wrapped(BaseCommand): def run(self): func() _Wrapped.__name__ = name func.__name__ = name cmdclass[name] = _Wrapped for name in ['pre_develop', 'post_develop', 'pre_dist', 'post_dist']: if locals()[name]: _make_command(name, locals()[name]) cmdclass['ensure_targets'] = ensure_targets(ensured_targets or []) skips = skip_if_exists or [] should_skip = skips and all(Path(path).exists() for path in skips) def _make_wrapper(klass, pre_build, post_build): class _Wrapped(klass): def run(self): if pre_build and not should_skip: self.run_command(pre_build.__name__) if klass != develop: self.run_command('ensure_targets') klass.run(self) if post_build and not should_skip: self.run_command(post_build.__name__) cmdclass[klass.__name__] = _Wrapped if pre_develop or post_develop: _make_wrapper(develop, pre_develop, post_develop) if pre_dist or post_dist or ensured_targets: _make_wrapper(sdist, pre_dist, post_dist) if bdist_wheel: _make_wrapper(bdist_wheel, pre_dist, post_dist) return cmdclass def npm_builder(path=None, build_dir=None, source_dir=None, build_cmd='build', force=False, npm=None): """Build function factory for managing an npm installation. Note: The function is a no-op if the `--skip-npm` cli flag is used. Parameters ---------- path: str, optional The base path of the node package. Defaults to the current directory. build_dir: str, optional The target build directory. If this and source_dir are given, the JavaScript will only be build if necessary. source_dir: str, optional The source code directory. build_cmd: str, optional The npm command to build assets to the build_dir. npm: str or list, optional. The npm executable name, or a tuple of ['node', executable]. Returns ------- A build function to use with `wrap_installers` """ def builder(): if skip_npm: log.info('Skipping npm-installation') return node_package = path or os.path.abspath(os.getcwd()) node_modules = pjoin(node_package, 'node_modules') is_yarn = os.path.exists(pjoin(node_package, 'yarn.lock')) if is_yarn and not which('yarn'): log.warn('yarn not found, ignoring yarn.lock file') is_yarn = False npm_cmd = npm if npm is None: if is_yarn: npm_cmd = ['yarn'] else: npm_cmd = ['npm'] elif isinstance(npm, str): npm_cmd = [npm] if not which(npm_cmd[0]): log.error("`{0}` unavailable. If you're running this command " "using sudo, make sure `{0}` is available to sudo" .format(npm_cmd[0])) return if build_dir and source_dir and not force: should_build = is_stale(build_dir, source_dir) else: should_build = True if should_build: log.info('Installing build dependencies with npm. This may ' 'take a while...') run(npm_cmd + ['install'], cwd=node_package) if build_cmd: run(npm_cmd + ['run', build_cmd], cwd=node_package) return builder # --------------------------------------------------------------------------- # Utility Functions # --------------------------------------------------------------------------- def get_data_files(data_specs, *, top=None, exclude=None): """Expand data file specs into valid data files metadata. Parameters ---------- data_files_spec: list A list of (path, dname, pattern) tuples where the path is the `data_files` install path, dname is the source directory, and the pattern is a glob pattern. top: str, optional The top directory exclude: func, optional Function used to test whether to exclude a file Returns ------- A valid list of data_files items. """ return _get_data_files(data_specs, None, top=top, exclude=exclude) def get_version(fpath, name='__version__'): """Get the version of the package from the given file by extracting the given `name`. """ # Try to get it from a static import first try: module = StaticModule(fpath.replace(os.sep, '.').replace('.py', '')) return getattr(module, name) except Exception as e: pass path = os.path.realpath(fpath) version_ns = {} with io.open(path, encoding="utf8") as f: exec(f.read(), {}, version_ns) return version_ns[name] def run(cmd, **kwargs): """Echo a command before running it.""" log.info('> ' + list2cmdline(cmd)) kwargs.setdefault('shell', os.name == 'nt') if not isinstance(cmd, (list, tuple)): cmd = shlex.split(cmd, posix=os.name!='nt') if not os.path.isabs(cmd[0]): # If a command is not an absolute path find it first. cmd_path = which(cmd[0]) if not cmd_path: raise ValueError("Aborting. Could not find cmd (%s) in path. " "If command is not expected to be in user's path, " "use an absolute path." % cmd[0]) cmd[0] = cmd_path return subprocess.check_call(cmd, **kwargs) def is_stale(target, source): """Test whether the target file/directory is stale based on the source file/directory. """ if not os.path.exists(target): return True target_mtime = recursive_mtime(target) or 0 return compare_recursive_mtime(source, cutoff=target_mtime) class BaseCommand(Command): """Empty command because Command needs subclasses to override too much""" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def get_inputs(self): return [] def get_outputs(self): return [] def combine_commands(*commands): """Return a Command that combines several commands.""" class CombinedCommand(BaseCommand): def initialize_options(self): self.commands = [] for C in commands: self.commands.append(C(self.distribution)) for c in self.commands: c.initialize_options() def finalize_options(self): for c in self.commands: c.finalize_options() def run(self): for c in self.commands: c.run() return CombinedCommand def compare_recursive_mtime(path, cutoff, newest=True): """Compare the newest/oldest mtime for all files in a directory. Cutoff should be another mtime to be compared against. If an mtime that is newer/older than the cutoff is found it will return True. E.g. if newest=True, and a file in path is newer than the cutoff, it will return True. """ if os.path.isfile(path): mt = mtime(path) if newest: if mt > cutoff: return True elif mt < cutoff: return True for dirname, _, filenames in os.walk(path, topdown=False): for filename in filenames: mt = mtime(pjoin(dirname, filename)) if newest: # Put outside of loop? if mt > cutoff: return True elif mt < cutoff: return True return False def recursive_mtime(path, newest=True): """Gets the newest/oldest mtime for all files in a directory.""" if os.path.isfile(path): return mtime(path) current_extreme = None for dirname, dirnames, filenames in os.walk(path, topdown=False): for filename in filenames: mt = mtime(pjoin(dirname, filename)) if newest: # Put outside of loop? if mt >= (current_extreme or mt): current_extreme = mt elif mt <= (current_extreme or mt): current_extreme = mt return current_extreme def mtime(path): """shorthand for mtime""" return os.stat(path).st_mtime def skip_if_exists(paths, CommandClass): """Skip a command if list of paths exists.""" def should_skip(): return all(Path(path).exists() for path in paths) class SkipIfExistCommand(Command): def initialize_options(self): if not should_skip(): self.command = CommandClass(self.distribution) self.command.initialize_options() else: self.command = None def finalize_options(self): if self.command is not None: self.command.finalize_options() def run(self): if self.command is not None: self.command.run() return SkipIfExistCommand def ensure_targets(targets): """Return a Command that checks that certain files exist. Raises a ValueError if any of the files are missing. Note: The check is skipped if the `--skip-npm` flag is used. """ class TargetsCheck(BaseCommand): def run(self): if skip_npm: log.info('Skipping target checks') return missing = [t for t in targets if not os.path.exists(t)] if missing: raise ValueError(('missing files: %s' % missing)) return TargetsCheck # --------------------------------------------------------------------------- # Deprecated Functions # --------------------------------------------------------------------------- @deprecated(deprecated_in="0.11", removed_in="2.0", current_version=__version__, details="Parse the version info as described in `get_version_info` docstring") def get_version_info(version_str): """DEPRECATED: Get a version info tuple given a version string Use something like the following instead: ``` import re # Version string must appear intact for tbump versioning __version__ = '1.4.0.dev0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' match = re.match(pattern, __version__) parts = [int(match[part]) for part in ['major', 'minor', 'patch']] if match['rest']: parts.append(match['rest']) version_info = tuple(parts) ``` """ match = VERSION_REGEX.match(version_str) if not match: raise ValueError(f'Invalid version "{version_str}"') release = match['release'] version_info = [int(p) for p in release.split('.')] if release != version_str: version_info.append(version_str[len(release):]) return tuple(version_info) @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Use `BaseCommand` directly instead") def command_for_func(func): """Create a command that calls the given function.""" class FuncCommand(BaseCommand): def run(self): func() update_package_data(self.distribution) return FuncCommand @deprecated(deprecated_in="0.7", removed_in="1.0", current_version=__version__, details="Use `setuptools` `python_requires` instead") def ensure_python(specs): """Given a list of range specifiers for python, ensure compatibility. """ if sys.version_info >= (3, 10): raise RuntimeError("ensure_python is deprecated and not compatible with Python 3.10+") if not isinstance(specs, (list, tuple)): specs = [specs] v = sys.version_info part = '%s.%s' % (v.major, v.minor) for spec in specs: if part == spec: return try: if eval(part + spec): return except SyntaxError: pass raise ValueError('Python version %s unsupported' % part) @deprecated(deprecated_in="0.7", removed_in="1.0", current_version=__version__, details="Use `setuptools.find_packages` instead") def find_packages(top): """ Find all of the packages. """ from setuptools import find_packages as fp return fp(top) @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Use `use_package_data=True` and `MANIFEST.in` instead") def update_package_data(distribution): """update build_py options to get package_data changes""" build_py = distribution.get_command_obj('build_py') build_py.finalize_options() @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Not needed") class bdist_egg_disabled(bdist_egg): """Disabled version of bdist_egg Prevents setup.py install performing setuptools' default easy_install, which it should never ever do. """ def run(self): sys.exit("Aborting implicit building of eggs. Use `pip install .` " " to install from source.") @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="""" Use `wrap_installers` to handle prebuild steps in cmdclass. Use `get_data_files` to handle data files. Use `include_package_data=True` and `MANIFEST.in` for package data. """) def create_cmdclass(prerelease_cmd=None, package_data_spec=None, data_files_spec=None, exclude=None): """Create a command class with the given optional prerelease class. Parameters ---------- prerelease_cmd: (name, Command) tuple, optional The command to run before releasing. package_data_spec: dict, optional A dictionary whose keys are the dotted package names and whose values are a list of glob patterns. data_files_spec: list, optional A list of (path, dname, pattern) tuples where the path is the `data_files` install path, dname is the source directory, and the pattern is a glob pattern. exclude: function A function which takes a string filename and returns True if the file should be excluded from package data and data files, False otherwise. Notes ----- We use specs so that we can find the files *after* the build command has run. The package data glob patterns should be relative paths from the package folder containing the __init__.py file, which is given as the package name. e.g. `dict(foo=['bar/*', 'baz/**'])` The data files directories should be absolute paths or relative paths from the root directory of the repository. Data files are specified differently from `package_data` because we need a separate path entry for each nested folder in `data_files`, and this makes it easier to parse. e.g. `('share/foo/bar', 'pkgname/bizz, '*')` """ wrapped = [prerelease_cmd] if prerelease_cmd else [] if package_data_spec or data_files_spec: wrapped.append('handle_files') wrapper = functools.partial(_wrap_command, wrapped) handle_files = _get_file_handler(package_data_spec, data_files_spec, exclude) develop_handler = _get_develop_handler() if 'bdist_egg' in sys.argv: egg = wrapper(bdist_egg, strict=True) else: egg = bdist_egg_disabled is_repo = os.path.exists('.git') cmdclass = dict( build_py=wrapper(build_py, strict=is_repo), bdist_egg=egg, sdist=wrapper(sdist, strict=True), handle_files=handle_files, ) if bdist_wheel: cmdclass['bdist_wheel'] = wrapper(bdist_wheel, strict=True) cmdclass['develop'] = wrapper(develop_handler, strict=True) return cmdclass @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Use `npm_builder` and `wrap_installers`") def install_npm(path=None, build_dir=None, source_dir=None, build_cmd='build', force=False, npm=None): """Return a Command for managing an npm installation. Note: The command is skipped if the `--skip-npm` flag is used. Parameters ---------- path: str, optional The base path of the node package. Defaults to the current directory. build_dir: str, optional The target build directory. If this and source_dir are given, the JavaScript will only be build if necessary. source_dir: str, optional The source code directory. build_cmd: str, optional The npm command to build assets to the build_dir. npm: str or list, optional. The npm executable name, or a tuple of ['node', executable]. """ builder = npm_builder(path=path, build_dir=build_dir, source_dir=source_dir, build_cmd=build_cmd, force=force, npm=npm) class NPM(BaseCommand): description = 'install package.json dependencies using npm' def run(self): builder() return NPM # --------------------------------------------------------------------------- # Private Functions # --------------------------------------------------------------------------- @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Use `npm_builder` and `wrap_installers`") def _wrap_command(cmds, cls, strict=True): """Wrap a setup command Parameters ---------- cmds: list(str) The names of the other commands to run prior to the command. strict: boolean, optional Whether to raise errors when a pre-command fails. """ class WrappedCommand(cls): def run(self): if not getattr(self, 'uninstall', None): try: [self.run_command(cmd) for cmd in cmds] except Exception: if strict: raise else: pass # update package data update_package_data(self.distribution) result = cls.run(self) return result return WrappedCommand @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Use `npm_builder` and `wrap_installers`") def _get_file_handler(package_data_spec, data_files_spec, exclude=None): """Get a package_data and data_files handler command. """ class FileHandler(BaseCommand): def run(self): package_data = self.distribution.package_data package_spec = package_data_spec or dict() for (key, patterns) in package_spec.items(): files = _get_package_data(key, patterns) if exclude is not None: files = [f for f in files if not exclude(f)] package_data[key] =files self.distribution.data_files = _get_data_files( data_files_spec, self.distribution.data_files, exclude=exclude ) return FileHandler @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Use `npm_builder` and `wrap_installers`") def _get_develop_handler(): """Get a handler for the develop command""" class _develop(develop): def install_for_development(self): self.finalize_options() super(_develop, self).install_for_development() self.run_command('handle_files') prefix = self.install_base or self.prefix or sys.prefix for target_dir, filepaths in self.distribution.data_files: for filepath in filepaths: filename = os.path.basename(filepath) target = os.path.join(prefix, target_dir, filename) self.mkpath(os.path.dirname(target)) outf, copied = self.copy_file(filepath, target) return _develop def _glob_pjoin(*parts): """Join paths for glob processing""" if parts[0] in ('.', ''): parts = parts[1:] return pjoin(*parts).replace(os.sep, '/') def _get_data_files(data_specs, existing, *, top=None, exclude=None): """Expand data file specs into valid data files metadata. Parameters ---------- data_specs: list of tuples See [create_cmdclass] for description. existing: list of tuples The existing distribution data_files metadata. top: str, optional The top directory exclude: func, optional Function used to test whether to exclude a file Returns ------- A valid list of data_files items. """ if top is None: top = os.path.abspath(os.getcwd()) # Extract the existing data files into a staging object. file_data = defaultdict(list) for (path, files) in existing or []: file_data[path] = files # Extract the files and assign them to the proper data # files path. for (path, dname, pattern) in data_specs or []: if os.path.isabs(dname): dname = os.path.relpath(dname, top) dname = dname.replace(os.sep, '/').rstrip('/') offset = 0 if dname in ('.', '') else len(dname) + 1 files = _get_files(_glob_pjoin(dname, pattern), top=top) for fname in files: # Normalize the path. root = os.path.dirname(fname) full_path = _glob_pjoin(path, root[offset:]) if full_path.endswith('/'): full_path = full_path[:-1] if exclude is not None and exclude(fname): continue file_data[full_path].append(fname) # Construct the data files spec. data_files = [] for (path, files) in file_data.items(): data_files.append((path, files)) return data_files def _get_files(file_patterns, top=None): """Expand file patterns to a list of paths. Parameters ----------- file_patterns: list or str A list of glob patterns for the data file locations. The globs can be recursive if they include a `**`. They should be relative paths from the top directory or absolute paths. top: str the directory to consider for data files Note: Files in `node_modules` are ignored. """ if top is None: top = os.path.abspath(os.getcwd()) if not isinstance(file_patterns, (list, tuple)): file_patterns = [file_patterns] for i, p in enumerate(file_patterns): if os.path.isabs(p): file_patterns[i] = os.path.relpath(p, top) matchers = [_compile_pattern(p) for p in file_patterns] files = set() for root, dirnames, filenames in os.walk(top): # Don't recurse into node_modules if 'node_modules' in dirnames: dirnames.remove('node_modules') for m in matchers: for filename in filenames: fn = os.path.relpath(_glob_pjoin(root, filename), top) fn = fn.replace(os.sep, '/') if m(fn): files.add(fn) return list(files) @deprecated(deprecated_in="0.8", removed_in="1.0", current_version=__version__, details="Use `npm_builder` and `wrap_installers`") def _get_package_data(root, file_patterns=None): """Expand file patterns to a list of `package_data` paths. Parameters ----------- root: str The relative path to the package root from the current dir. file_patterns: list or str, optional A list of glob patterns for the data file locations. The globs can be recursive if they include a `**`. They should be relative paths from the root or absolute paths. If not given, all files will be used. Note: Files in `node_modules` are ignored. """ if file_patterns is None: file_patterns = ['*'] return _get_files(file_patterns, _glob_pjoin(os.path.abspath(os.getcwd()), root)) def _compile_pattern(pat, ignore_case=True): """Translate and compile a glob pattern to a regular expression matcher.""" if isinstance(pat, bytes): pat_str = pat.decode('ISO-8859-1') res_str = _translate_glob(pat_str) res = res_str.encode('ISO-8859-1') else: res = _translate_glob(pat) flags = re.IGNORECASE if ignore_case else 0 return re.compile(res, flags=flags).match def _iexplode_path(path): """Iterate over all the parts of a path. Splits path recursively with os.path.split(). """ (head, tail) = os.path.split(path) if not head or (not tail and head == path): if head: yield head if tail or not head: yield tail return for p in _iexplode_path(head): yield p yield tail def _translate_glob(pat): """Translate a glob PATTERN to a regular expression.""" translated_parts = [] for part in _iexplode_path(pat): translated_parts.append(_translate_glob_part(part)) os_sep_class = '[%s]' % re.escape(SEPARATORS) res = _join_translated(translated_parts, os_sep_class) return '(?ms){res}\\Z'.format(res=res) def _join_translated(translated_parts, os_sep_class): """Join translated glob pattern parts. This is different from a simple join, as care need to be taken to allow ** to match ZERO or more directories. """ res = '' for part in translated_parts[:-1]: if part == '.*': # drop separator, since it is optional # (** matches ZERO or more dirs) res += part else: res += part + os_sep_class if translated_parts[-1] == '.*': # Final part is ** res += '.+' # Follow stdlib/git convention of matching all sub files/directories: res += '({os_sep_class}?.*)?'.format(os_sep_class=os_sep_class) else: res += translated_parts[-1] return res def _translate_glob_part(pat): """Translate a glob PATTERN PART to a regular expression.""" # Code modified from Python 3 standard lib fnmatch: if pat == '**': return '.*' i, n = 0, len(pat) res = [] while i < n: c = pat[i] i = i + 1 if c == '*': # Match anything but path separators: res.append('[^%s]*' % SEPARATORS) elif c == '?': res.append('[^%s]?' % SEPARATORS) elif c == '[': j = i if j < n and pat[j] == '!': j = j + 1 if j < n and pat[j] == ']': j = j + 1 while j < n and pat[j] != ']': j = j + 1 if j >= n: res.append('\\[') else: stuff = pat[i:j].replace('\\', '\\\\') i = j + 1 if stuff[0] == '!': stuff = '^' + stuff[1:] elif stuff[0] == '^': stuff = '\\' + stuff res.append('[%s]' % stuff) else: res.append(re.escape(c)) return ''.join(res) jupyter-packaging-0.11.1/pyproject.toml000066400000000000000000000001571414452047400201420ustar00rootroot00000000000000[build-system] requires = ["setuptools>=46.4.0", "wheel", "packaging"] build-backend = "setuptools.build_meta" jupyter-packaging-0.11.1/setup.cfg000066400000000000000000000023251414452047400170460ustar00rootroot00000000000000[metadata] name = jupyter_packaging version = attr: jupyter_packaging.setupbase.__version__ description = Jupyter Packaging Utilities. long_description = file: README.md long_description_content_type = text/markdown license = BSD 3-Clause License author = Jupyter Development Team author_email = jupyter@googlegroups.com url = http://jupyter.org platforms = Linux, Mac OS X, Windows keywords = ipython, jupyter, packaging classifiers = Intended Audience :: Developers Intended Audience :: System Administrators Intended Audience :: Science/Research License :: OSI Approved :: BSD License Programming Language :: Python Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 [options] zip_safe = False include_package_data = True packages = find: python_requires = >=3.7 install_requires = packaging tomlkit setuptools>=46.4.0 wheel deprecation [options.extras_require] test = build; coverage; pytest; pytest-cov; pytest-mock [bdist_wheel] universal=1 [manifix] known_excludes = .git* *.pyc build dist appveyor.yml .travis.yml .cache .pytest_cache jupyter-packaging-0.11.1/setup.py000066400000000000000000000002341414452047400167340ustar00rootroot00000000000000# coding: utf-8 # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from setuptools import setup setup() jupyter-packaging-0.11.1/tests/000077500000000000000000000000001414452047400163655ustar00rootroot00000000000000jupyter-packaging-0.11.1/tests/conftest.py000066400000000000000000000162371414452047400205750ustar00rootroot00000000000000import json import os import pathlib import sys from pytest import fixture from subprocess import run HERE = pathlib.Path(__file__).resolve() NAME = "jupyter_packaging_test_foo" PACKAGE_JSON = json.dumps(dict( name="foo", version="0.1.0", scripts=dict(build=f"echo $(DATE) > {NAME}/generated.js") )) @fixture(scope="session", autouse=True) def clear_pip_cache(): run([sys.executable, '-m', 'pip', 'cache', 'remove', 'jupyter_packaging']) @fixture def pyproject_toml(): """A fixture that enables other fixtures to build mock packages with that depend on this package. """ root_path = HERE.joinpath("../..").resolve() return """ [build-system] requires = ["jupyter_packaging@file://%s"] build-backend = "setuptools.build_meta" """ % str(root_path).replace(os.sep, '/') setup_cfg_maker = lambda name=NAME: """ [metadata] name = {name} version = 0.1 author = Jupyter Development Team author_email = jupyter@googlegroups.com url = http://jupyter.org description="foo package", long_description="long_description", long_description_content_type="text/markdown", [options] zip_safe = False include_package_data = True py_modules = foo python_requires = >=3.6 """.format(name=name) setup_maker = lambda name=NAME, data_files_spec=None, pre_dist=None, ensured_targets=None, skip_if_exists=None, **kwargs: """ from jupyter_packaging import get_data_files, wrap_installers, npm_builder import setuptools import os def exclude(filename): return os.path.basename(filename) == "exclude.py" data_files=get_data_files({data_files_spec}, exclude=exclude) cmdclass = wrap_installers(pre_dist={pre_dist}, ensured_targets={ensured_targets}, skip_if_exists={skip_if_exists}) setuptools.setup(data_files=data_files, cmdclass=cmdclass, {setup_args}) """.format( name=name, data_files_spec=data_files_spec, pre_dist=pre_dist or 'lambda: print', ensured_targets=ensured_targets or [], skip_if_exists=skip_if_exists or [], setup_args="".join(['{}={},\n\t'.format(key, str(val)) for key, val in kwargs.items()]) ) setup_maker_deprecated = lambda name=NAME, data_files_spec=None, pre_dist=None, ensured_targets=None, **kwargs: """ from jupyter_packaging import create_cmdclass, install_npm import setuptools import os def exclude(filename): return os.path.basename(filename) == "exclude.py" cmdclass = create_cmdclass('jsdeps', data_files_spec={data_files_spec}, exclude=exclude) cmdclass['jsdeps'] = install_npm() setuptools.setup(cmdclass=cmdclass, {setup_args}) """.format( name=name, data_files_spec=data_files_spec, setup_args="".join(['{}={},\n\t'.format(key, str(val)) for key, val in kwargs.items()]) ) def make_package_base(tmp_path, pyproject_toml, setup_func=setup_maker, include_js=False): def do_stuff( name=NAME, data_files=None, data_files_spec=None, ensured_targets=None, skip_if_exists=None, py_module=False ): # Create the package directory. pkg = tmp_path.joinpath('package') pkg.mkdir() # What type of a package is this, single module or nested package? setup_args = {} if py_module: setup_args.update({"py_module": [NAME]}) pkg.joinpath(f'{NAME}_foo.py').write_text('print("hello, world!")') pkg.joinpath('MANIFEST.in').write_text('recursive-include share *.*') else: setup_args.update({"packages": "setuptools.find_packages('.')"}) mod = pkg.joinpath(NAME) mod.mkdir() mod.joinpath('__init__.py').write_text('') mod.joinpath('main.py').write_text('print("hello, world!")') # path that is meant to be excluded mod.joinpath("exclude.py") pkg.joinpath('MANIFEST.in').write_text('recursive-include share *.*') # Fill the package with content. # 1. Add a setup.py setuppy = pkg.joinpath("setup.py") # Pass the data_file spec to the setup.py pre_dist = None if include_js: pre_dist = 'npm_builder()' setup_content = setup_func( name=name, data_files_spec=data_files_spec, ensured_targets=ensured_targets, skip_if_exists=skip_if_exists, pre_dist=pre_dist, **setup_args ) setuppy.write_text(setup_content) # 2. Add pyproject.toml to package. pkg.joinpath('pyproject.toml').write_text(pyproject_toml) # 3. Add setup.cfg to package. pkg.joinpath('setup.cfg').write_text(setup_cfg_maker(name=name)) # 4. Add datafiles content. manifest = pkg / 'MANIFEST.in' if data_files: for datafile_path in data_files: data_file = pkg.joinpath(datafile_path) data_dir = data_file.parent data_dir.mkdir(parents=True, exist_ok=True) data_file.write_text("hello, world!") text = manifest.read_text() manifest.write_text(f'{text}\ninclude {datafile_path}') # 5. Add package.json if needed. if include_js: package_json = pkg.joinpath("package.json") package_json.write_text(PACKAGE_JSON, encoding='utf-8') text = manifest.read_text() manifest.write_text(f'{text}\ninclude {name}/generated.js') return pkg return do_stuff @fixture def make_package(tmp_path, pyproject_toml): """A callable fixture that creates a mock python package in tmp_path and returns the package directory """ return make_package_base(tmp_path, pyproject_toml) @fixture def make_package_deprecated(tmp_path, pyproject_toml): """A callable fixture that creates a mock python package in tmp_path and returns the package directory """ return make_package_base(tmp_path, pyproject_toml, setup_func=setup_maker_deprecated, include_js=True) @fixture def make_hybrid_package(tmp_path, pyproject_toml): """A callable fixture that creates a mock hybrid package in tmp_path and returns the package directory """ return make_package_base(tmp_path, pyproject_toml, include_js=True) @fixture def source_dir(tmpdir): source = tmpdir.mkdir('source') source.join('file1.txt').write("original content") source.join('file2.txt').write("original content") sub = source.mkdir('sub') sub.join('subfile1.txt').write("original content") sub.join('subfile2.txt').write("original content") source.mkdir('node_modules') sub2 = source.mkdir('node_modules', 'lol') sub2.join('index.js').write("use strict;") for p in source.visit(): p.setmtime(10000) return source @fixture def destination_dir(tmpdir): destination = tmpdir.mkdir('destination') destination.join('file1.rtf').write("original content") destination.join('file2.rtf').write("original content") sub = destination.mkdir('sub') sub.join('subfile1.rtf').write("original content") sub.join('subfile2.rtf').write("original content") destination.mkdir('sub2') sub2 = destination.mkdir('sub2', 'lol') sub2.join('static.html').write( "original content") for p in destination.visit(): p.setmtime(20000) return destination jupyter-packaging-0.11.1/tests/test_build_api.py000066400000000000000000000111361414452047400217300ustar00rootroot00000000000000import os import sys from subprocess import check_call from unittest.mock import patch, call import pytest from jupyter_packaging.build_api import build_wheel, build_sdist TOML_CONTENT = """ [tool.jupyter-packaging.builder] factory = "foo.main" [tool.jupyter-packaging.build-args] fizz = "buzz" """ FOO_CONTENT = r""" from pathlib import Path def main(fizz=None): Path('foo.txt').write_text(f'fizz={fizz}', encoding='utf-8') """ BAD_CONTENT = """ [tool.jupyter-packaging.builder] bar = "foo.main" """ ENSURED_CONTENT = """ [tool.jupyter-packaging.options] ensured-targets = ["foo.txt"] """ SKIP_IF_EXISTS = """ [tool.jupyter-packaging.options] skip-if-exists = ["foo.txt"] """ def test_build_wheel_no_toml(tmp_path): os.chdir(tmp_path) orig_wheel = patch('jupyter_packaging.build_api.orig_build_wheel') build_wheel(tmp_path) orig_wheel.assert_called_with(tmp_path, config_settings=None, metadata_directory=None) def test_build_wheel(tmp_path, mocker): os.chdir(tmp_path) tmp_path.joinpath('foo.py').write_text(FOO_CONTENT) tmp_path.joinpath('pyproject.toml').write_text(TOML_CONTENT + ENSURED_CONTENT, encoding='utf-8') orig_wheel = mocker.patch('jupyter_packaging.build_api.orig_build_wheel') build_wheel(tmp_path) orig_wheel.assert_called_with(tmp_path, config_settings=None, metadata_directory=None) data = tmp_path.joinpath('foo.txt').read_text(encoding='utf-8') assert data == 'fizz=buzz' content = TOML_CONTENT.replace("buzz", "fizz") + SKIP_IF_EXISTS tmp_path.joinpath('pyproject.toml').write_text(content, encoding='utf-8') build_wheel(tmp_path) data = tmp_path.joinpath('foo.txt').read_text(encoding='utf-8') assert data == 'fizz=buzz' def test_build_wheel_bad_toml(tmp_path, mocker): os.chdir(tmp_path) tmp_path.joinpath('foo.py').write_text(FOO_CONTENT) tmp_path.joinpath('pyproject.toml').write_text(BAD_CONTENT, encoding='utf-8') orig_wheel = mocker.patch('jupyter_packaging.build_api.orig_build_wheel') with pytest.raises(ValueError): build_wheel(tmp_path) orig_wheel.assert_not_called() def test_build_wheel_no_toml(tmp_path, mocker): os.chdir(tmp_path) orig_wheel = mocker.patch('jupyter_packaging.build_api.orig_build_wheel') build_wheel(tmp_path) orig_wheel.assert_called_with(tmp_path, config_settings=None, metadata_directory=None) def test_build_sdist(tmp_path, mocker): os.chdir(tmp_path) tmp_path.joinpath('foo.py').write_text(FOO_CONTENT) tmp_path.joinpath('pyproject.toml').write_text(TOML_CONTENT + ENSURED_CONTENT, encoding='utf-8') orig_sdist = mocker.patch('jupyter_packaging.build_api.orig_build_sdist') build_sdist(tmp_path) orig_sdist.assert_called_with(tmp_path, config_settings=None) data = tmp_path.joinpath('foo.txt').read_text(encoding='utf-8') assert data == 'fizz=buzz' def test_build_sdist_bad_toml(tmp_path, mocker): os.chdir(tmp_path) tmp_path.joinpath('foo.py').write_text(FOO_CONTENT) tmp_path.joinpath('pyproject.toml').write_text(BAD_CONTENT, encoding='utf-8') orig_sdist = mocker.patch('jupyter_packaging.build_api.orig_build_sdist') with pytest.raises(ValueError): build_sdist(tmp_path) orig_sdist.assert_not_called() def test_build_sdist_no_toml(tmp_path, mocker): os.chdir(tmp_path) orig_sdist = mocker.patch('jupyter_packaging.build_api.orig_build_sdist') build_sdist(tmp_path) orig_sdist.assert_called_with(tmp_path, config_settings=None) def test_build_package(make_package): package_dir = make_package() pyproject = package_dir / "pyproject.toml" text = pyproject.read_text(encoding='utf-8') text = text.replace('setuptools.build_meta', 'jupyter_packaging.build_api') text += TOML_CONTENT pyproject.write_text(text, encoding='utf-8') package_dir.joinpath('foo.py').write_text(FOO_CONTENT, encoding='utf-8') check_call([sys.executable, '-m', 'build'], cwd=package_dir) data = package_dir.joinpath('foo.txt').read_text(encoding='utf-8') assert data == 'fizz=buzz' def test_deprecated_metadata(make_package): package_dir = make_package() pyproject = package_dir / "pyproject.toml" text = pyproject.read_text(encoding='utf-8') text = text.replace('setuptools.build_meta', 'jupyter_packaging.build_api') text += TOML_CONTENT text = text.replace('factory =', 'func =') pyproject.write_text(text, encoding='utf-8') package_dir.joinpath('foo.py').write_text(FOO_CONTENT, encoding='utf-8') check_call([sys.executable, '-m', 'build'], cwd=package_dir) data = package_dir.joinpath('foo.txt').read_text(encoding='utf-8') assert data == 'fizz=buzz' jupyter-packaging-0.11.1/tests/test_core_functions.py000066400000000000000000000055601414452047400230240ustar00rootroot00000000000000import os from unittest.mock import patch, call import pytest from setuptools.dist import Distribution from jupyter_packaging.setupbase import npm_builder, wrap_installers def test_wrap_installers(): called = False def func(): nonlocal called called = True cmd_class = wrap_installers(pre_dist=func, pre_develop=func, post_dist=func, post_develop=func) for name in ['pre_dist', 'pre_develop', 'post_dist', 'post_develop']: cmd_class[name](Distribution()).run() assert called called = False def test_npm_builder(mocker): which = mocker.patch('jupyter_packaging.setupbase.which') run = mocker.patch('jupyter_packaging.setupbase.run') builder = npm_builder() which.return_value = ['foo'] builder() cwd=os.getcwd() run.assert_has_calls([ call(['npm', 'install'], cwd=cwd), call(['npm', 'run', 'build'], cwd=cwd) ]) def test_npm_build_skip(mocker): which = mocker.patch('jupyter_packaging.setupbase.which') run = mocker.patch('jupyter_packaging.setupbase.run') mocker.patch('jupyter_packaging.setupbase.skip_npm', True) builder = npm_builder() which.return_value = ['foo'] builder() run.assert_not_called() def test_npm_builder_yarn(tmp_path, mocker): which = mocker.patch('jupyter_packaging.setupbase.which') run = mocker.patch('jupyter_packaging.setupbase.run') tmp_path.joinpath('yarn.lock').write_text('hello') builder = npm_builder(path=tmp_path) which.return_value = ['foo'] builder() run.assert_has_calls([ call(['yarn', 'install'], cwd=tmp_path), call(['yarn', 'run', 'build'], cwd=tmp_path) ]) def test_npm_builder_missing_yarn(tmp_path, mocker): which = mocker.patch('jupyter_packaging.setupbase.which') run = mocker.patch('jupyter_packaging.setupbase.run') tmp_path.joinpath('yarn.lock').write_text('hello') builder = npm_builder(path=tmp_path) which.side_effect = ['', 'foo'] builder() run.assert_has_calls([ call(['npm', 'install'], cwd=tmp_path), call(['npm', 'run', 'build'], cwd=tmp_path) ]) def test_npm_builder_not_stale(tmp_path, mocker): which = mocker.patch('jupyter_packaging.setupbase.which') run = mocker.patch('jupyter_packaging.setupbase.run') is_stale = mocker.patch('jupyter_packaging.setupbase.is_stale') is_stale.return_value = False builder = npm_builder(build_dir=tmp_path, source_dir=tmp_path) which.return_value = ['foo'] builder() run.assert_not_called() def test_npm_builder_no_npm(mocker): which = mocker.patch('jupyter_packaging.setupbase.which') run = mocker.patch('jupyter_packaging.setupbase.run') is_stale = mocker.patch('jupyter_packaging.setupbase.is_stale') is_stale.return_value = False builder = npm_builder() which.return_value = [] builder() run.assert_not_called() jupyter-packaging-0.11.1/tests/test_datafiles_install.py000066400000000000000000000054211414452047400234620ustar00rootroot00000000000000 import pytest import subprocess import shutil import pathlib import sys from deprecation import fail_if_not_removed data_files_combinations = [ ( # data file source ("share/test.txt",), # data file spec ("jupyter-packaging-test", "share", "**/*"), # data file target "jupyter-packaging-test/test.txt" ), ( # data file source ("share/test.txt",), # data file spec ("jupyter-packaging-test/level1", "share", "**/[a-z]est.txt"), # data file target "jupyter-packaging-test/level1/test.txt" ), ( # data file source ("level1/test/test.txt",), # data file spec ("jupyter-packaging-test", "level1/test", "**/*"), # data file target "jupyter-packaging-test/test.txt" ), ( # data file source ("level1/test/test.txt",), # data file spec ("jupyter-packaging-test/level1/level2", "level1/test", "**/*"), # data file target "jupyter-packaging-test//level1/level2/test.txt" ), ( # data file source ("level1/level2/test/test.txt",), # data file spec ("jupyter-packaging-test", "level1", "**/*"), # data file target "jupyter-packaging-test/level2/test/test.txt" ), ] @fail_if_not_removed @pytest.mark.parametrize( 'source,spec,target', data_files_combinations ) def test_develop(make_package_deprecated, source,spec,target): name = 'jupyter_packaging_test_foo' package_dir = make_package_deprecated(name=name, data_files=source, data_files_spec=[spec]) target_path = pathlib.Path(sys.prefix).joinpath(target) if target_path.exists(): shutil.rmtree(str(target_path.parent)) subprocess.check_output([shutil.which('pip'), 'install', '-e', '.'], cwd=str(package_dir)) assert target_path.exists() subprocess.check_output([shutil.which('pip'), 'uninstall', '-y', name], cwd=str(package_dir)) # This is something to fix later. uninstalling a package installed # with -e should ne removed. assert target_path.exists() shutil.rmtree(str(target_path.parent)) @pytest.mark.parametrize( 'source,spec,target', data_files_combinations ) def test_install(make_package, source,spec,target): name = 'jupyter_packaging_test_foo' package_dir = make_package(name=name, data_files=source, data_files_spec=[spec]) target_path = pathlib.Path(sys.prefix).joinpath(target) if target_path.exists(): shutil.rmtree(str(target_path.parent)) subprocess.check_output([shutil.which('pip'), 'install', '.'], cwd=str(package_dir)) assert target_path.exists() subprocess.check_output([shutil.which('pip'), 'uninstall', '-y', name], cwd=str(package_dir)) assert not target_path.exists() jupyter-packaging-0.11.1/tests/test_datafiles_paths.py000066400000000000000000000060221414452047400231310ustar00rootroot00000000000000import os from jupyter_packaging.setupbase import get_data_files def test_empty_relative_path(tmpdir): tmpdir.mkdir('sub1').join('a.json').write('') tmpdir.mkdir('sub2').join('b.json').write('') spec = [ ('my/target', '', '**/*.json') ] res = get_data_files(spec, top=str(tmpdir)) assert sorted(res) == [ ('my/target/sub1', ['sub1/a.json']), ('my/target/sub2', ['sub2/b.json']), ] def test_dot_relative_path(tmpdir): tmpdir.mkdir('sub1').join('a.json').write('') tmpdir.mkdir('sub2').join('b.json').write('') spec = [ ('my/target', '.', '**/*.json') ] res = get_data_files(spec, top=str(tmpdir)) assert sorted(res) == [ ('my/target/sub1', ['sub1/a.json']), ('my/target/sub2', ['sub2/b.json']), ] def test_subdir_relative_path(tmpdir): tmpdir.mkdir('sub1').join('a.json').write('') tmpdir.mkdir('sub2').join('b.json').write('') spec = [ ('my/target', 'sub1', '**/[a-z].json') ] res = get_data_files(spec, top=str(tmpdir)) assert sorted(res) == [ ('my/target', ['sub1/a.json']), ] def test_root_absolute_path(tmpdir): tmpdir.mkdir('sub1').join('a.json').write('') tmpdir.mkdir('sub2').join('b.json').write('') spec = [ ('my/target', str(tmpdir), '**/*.json') ] res = get_data_files(spec, top=str(tmpdir)) assert sorted(res) == [ ('my/target/sub1', ['sub1/a.json']), ('my/target/sub2', ['sub2/b.json']), ] def test_subdir_absolute_path(tmpdir): tmpdir.mkdir('sub1').join('a.json').write('') tmpdir.mkdir('sub2').join('b.json').write('') spec = [ ('my/target', str(tmpdir.join('sub1')), '**/*.json') ] res = get_data_files(spec, top=str(tmpdir)) assert sorted(res) == [ ('my/target', ['sub1/a.json']), ] def test_absolute_trailing_slash(tmpdir): maindir = tmpdir.mkdir('main') maindir.mkdir('sub1').join('a.json').write('') maindir.mkdir('sub2').join('b.json').write('') spec = [ ('my/target/', str(tmpdir) + '/', '**/*.*') ] res = get_data_files(spec, top=str(tmpdir)) assert sorted(res) == [ ('my/target/main/sub1', ['main/sub1/a.json']), ('my/target/main/sub2', ['main/sub2/b.json']), ] def test_relative_trailing_slash(tmpdir): maindir = tmpdir.mkdir('main') maindir.mkdir('sub1').join('a.json').write('') maindir.mkdir('sub2').join('b.json').write('') spec = [ ('my/target/', 'main/', '**/*.json') ] res = get_data_files(spec, top=str(tmpdir)) assert sorted(res) == [ ('my/target/sub1', ['main/sub1/a.json']), ('my/target/sub2', ['main/sub2/b.json']), ] def test_nested_source_dir(tmpdir): maindir = tmpdir.mkdir('main') maindir.mkdir('sub1').join('a.json').write('') maindir.mkdir('sub2').join('b.json').write('') spec = [ ('my/target', 'main/sub1', 'a.json') ] res = get_data_files(spec, top=str(tmpdir)) assert sorted(res) == [ ('my/target', ['main/sub1/a.json']), ] jupyter-packaging-0.11.1/tests/test_deprecated.py000066400000000000000000000076631414452047400221120ustar00rootroot00000000000000 import os import sys from unittest.mock import patch from deprecation import fail_if_not_removed import pytest from setuptools.dist import Distribution import jupyter_packaging.setupbase as pkg here = os.path.dirname(__file__) root = os.path.join(here, os.pardir) @fail_if_not_removed def test_finds_itself(): with pytest.warns(DeprecationWarning): assert ['jupyter_packaging'] == pkg.find_packages(root) def test_finds_subpackages(tmpdir): a = tmpdir.mkdir('packageA') sub_a1 = a.mkdir('sub1') sub_a2 = a.mkdir('sub2') b = tmpdir.mkdir('packageB') sub_b1 = b.mkdir('sub1') sub_b2 = b.mkdir('sub2') for d in (a, sub_a1, sub_a2, b, sub_b1, sub_b2): d.join('__init__.py').write('') # using sets ensure order won't matter expected = set([ 'packageA', 'packageA.sub1', 'packageA.sub2', 'packageB', 'packageB.sub1', 'packageB.sub2' ]) with pytest.warns(DeprecationWarning): found = set(pkg.find_packages(str(tmpdir))) assert expected == found def test_finds_only_direct_subpackages(tmpdir): a = tmpdir.mkdir('packageA') sub_a1 = a.mkdir('sub1') sub_a2 = a.mkdir('sub2') # No __init__.py in packageA: for d in (sub_a1, sub_a2): d.join('__init__.py').write('') expected = [] with pytest.warns(DeprecationWarning): assert expected == pkg.find_packages(str(tmpdir)) @pytest.mark.skipif(sys.version_info >= (3, 10), reason="Not compatible with Python 3.10+") def test_ensure_python(): pkg.ensure_python('>=3.6') pkg.ensure_python(['>=3.6', '>=3.5']) with pytest.raises(ValueError): pkg.ensure_python('<3.5') @pytest.mark.skipif(sys.version_info < (3, 10), reason="Tests RuntimeError for Python 3.10+") def test_ensure_python_310(): with pytest.raises(RuntimeError): pkg.ensure_python('>=3.6') def test_create_cmdclass(make_package_deprecated, mocker): source = ("share/test.txt",) spec = ("jupyter-packaging-test", "share", "**/*") target = "jupyter-packaging-test/test.txt" pkg_path = make_package_deprecated(data_files=source, data_files_spec=spec) os.chdir(pkg_path) cmdclass = pkg.create_cmdclass( package_data_spec=dict(foo="*.*"), data_files_spec=[spec], exclude=lambda x: False ) for name in ['build_py', 'handle_files', 'sdist', 'bdist_wheel']: assert name in cmdclass dist = Distribution() cmdclass['handle_files'](dist).run() assert dist.data_files == [('jupyter-packaging-test', ['share/test.txt'])] assert dist.package_data == {'foo': []} # Test installation of data_files in develop mode dist = Distribution() handler = cmdclass['handle_files'](dist) develop = cmdclass['develop'](dist) def run_command(name): cmdclass[name](dist).run() mocker.patch.object(pkg.develop, 'install_for_development') develop.run_command = run_command develop.install_for_development() assert dist.data_files == [('jupyter-packaging-test', ['share/test.txt'])] def test_command_for_func(): called = False def func(): nonlocal called called = True cmd = pkg.command_for_func(func) cmd(Distribution()).run() assert called def test_install_npm(): builder = pkg.install_npm() assert issubclass(builder, pkg.BaseCommand) def test__wrap_command(): called = False def func(self, cmd): nonlocal called called = True class TestCommand(pkg.BaseCommand): def run(self): pass cmd = pkg._wrap_command(['js'], TestCommand) cmd.run_command = func dist = Distribution() cmd(dist).run() assert called == True @fail_if_not_removed def test_get_version_info(): assert pkg.get_version_info('1.3.0') == (1,3,0) assert pkg.get_version_info('1.3.0a1') == (1,3,0,'a1') assert pkg.get_version_info('1.3.0.dev0') == (1,3,0,'.dev0') with pytest.raises(ValueError): pkg.get_version_info('1.3.0foo1') jupyter-packaging-0.11.1/tests/test_install.py000066400000000000000000000047061414452047400214530ustar00rootroot00000000000000import os import subprocess import shutil from pathlib import Path import sysconfig import pytest def test_install(make_package, tmp_path): name = 'jupyter_packaging_test_foo' ensured_targets=[f'{name}/main.py'] package_dir = make_package(name=name, ensured_targets=ensured_targets) subprocess.check_output([shutil.which('pip'), 'install', '.'], cwd=str(package_dir)) # Get site packages where the package is installed. sitepkg = Path(sysconfig.get_paths()["purelib"]) installed_file = sitepkg / f"{name}/main.py" assert installed_file.exists() excluded_file = sitepkg / f"{name}/exclude.py" assert not excluded_file.exists() subprocess.check_output([shutil.which('pip'), 'uninstall', name, '-y'], cwd=str(package_dir)) assert not installed_file.exists() def test_install_hybrid(make_hybrid_package, tmp_path): name = 'jupyter_packaging_test_foo' ensured_targets = [f"{name}/main.py", f"{name}/generated.js"] package_dir = make_hybrid_package(name=name, ensured_targets=ensured_targets, skip_if_exists=[f"{name}/generated.js"]) subprocess.check_output([shutil.which('pip'), 'install', '.'], cwd=str(package_dir)) # Get site packages where the package is installed. sitepkg = Path(sysconfig.get_paths()["purelib"]) installed_py_file = sitepkg / f"{name}/main.py" installed_js_file = sitepkg / f"{name}/generated.js" assert installed_py_file.exists() assert installed_js_file.exists() content = installed_js_file.read_text(encoding='utf-8') Path(f"{package_dir}/{name}/generated.js").write_text(content, encoding='utf-8') excluded_file = sitepkg / f"{name}/exclude.py" assert not excluded_file.exists() subprocess.check_output([shutil.which('pip'), 'install', '.'], cwd=str(package_dir)) assert content == installed_js_file.read_text(encoding='utf-8') subprocess.check_output([shutil.which('pip'), 'uninstall', name, '-y'], cwd=str(package_dir)) assert not installed_py_file.exists() assert not installed_js_file.exists() def test_install_missing(make_package, tmp_path): name = 'jupyter_packaging_test_foo' ensured_targets=[f'{name}/missing.py'] package_dir = make_package(name=name, ensured_targets=ensured_targets) with pytest.raises(subprocess.CalledProcessError): subprocess.check_output([shutil.which('pip'), 'install', '.'], cwd=str(package_dir)) subprocess.check_output([shutil.which('pip'), 'install', '-e', '.'], cwd=str(package_dir)) jupyter-packaging-0.11.1/tests/test_is_stale.py000066400000000000000000000053151414452047400216050ustar00rootroot00000000000000 from jupyter_packaging.setupbase import is_stale def test_destination_is_not_stale(source_dir, destination_dir): assert is_stale(str(destination_dir), str(source_dir)) is False def test_root_file_causes_stale(source_dir, destination_dir): source_dir.join('file1.txt').setmtime(30000) assert is_stale(str(destination_dir), str(source_dir)) is True def test_sub_file_causes_stale(source_dir, destination_dir): source_dir.join('sub', 'subfile2.txt').setmtime(30000) assert is_stale(str(destination_dir), str(source_dir)) is True def test_folder_mtime_does_not_prevent_stale(source_dir, destination_dir): source_dir.join('sub', 'subfile2.txt').setmtime(30000) destination_dir.setmtime(40000) destination_dir.join('sub').setmtime(40000) destination_dir.setmtime(40000) assert is_stale(str(destination_dir), str(source_dir)) is True def test_folder_mtime_does_not_cause_stale(source_dir, destination_dir): source_dir.setmtime(40000) source_dir.join('sub').setmtime(40000) source_dir.setmtime(40000) assert is_stale(str(destination_dir), str(source_dir)) is False # This behavior might not always be wanted? # The alternative is to check whether ALL files in destination is newer # than the newest file in source (more conservative). def test_only_newest_files_determine_stale(source_dir, destination_dir): source_dir.join('file1.txt').setmtime(30000) destination_dir.join('file1.rtf').setmtime(40000) assert is_stale(str(destination_dir), str(source_dir)) is False def test_unstale_on_equal(source_dir): assert is_stale(str(source_dir), str(source_dir)) is False def test_file_vs_dir(source_dir, destination_dir): assert is_stale(str(destination_dir.join('file1.rtf')), str(source_dir)) is False source_dir.join('file2.txt').setmtime(30000) assert is_stale(str(destination_dir.join('file1.rtf')), str(source_dir)) is True def test_dir_vs_file(source_dir, destination_dir): assert is_stale(str(destination_dir), str(source_dir.join('file1.txt'))) is False source_dir.join('file1.txt').setmtime(30000) assert is_stale(str(destination_dir), str(source_dir.join('file1.txt'))) is True def test_file_vs_file(source_dir, destination_dir): assert is_stale(str(destination_dir.join('file1.rtf')), str(source_dir.join('file1.txt'))) is False source_dir.join('file1.txt').setmtime(30000) assert is_stale(str(destination_dir.join('file1.rtf')), str(source_dir.join('file1.txt'))) is True def test_empty_dir(source_dir, tmpdir): empty_dir = tmpdir.mkdir('empty') assert is_stale(str(empty_dir), str(source_dir)) is True assert is_stale(str(source_dir), str(empty_dir)) is False assert is_stale(str(empty_dir), str(empty_dir)) is False jupyter-packaging-0.11.1/tests/test_main.py000066400000000000000000000002611414452047400207210ustar00rootroot00000000000000 from jupyter_packaging.__main__ import main def test_main_copies_setupbase(tmpdir): d = tmpdir.mkdir("sub") main([str(d)]) assert d.join('setupbase.py').check() jupyter-packaging-0.11.1/tests/test_utility_functions.py000066400000000000000000000044261414452047400235770ustar00rootroot00000000000000 from unittest.mock import patch import pytest import sys from setuptools.dist import Distribution from jupyter_packaging.setupbase import __file__ as path import jupyter_packaging.setupbase as pkg from utils import run_command def test_get_version(): version = pkg.get_version(path) assert version == pkg.__version__ def test_combine_commands(): class MockCommand(pkg.BaseCommand): called = 0 def run(self): MockCommand.called += 1 combined_klass = pkg.combine_commands(MockCommand, MockCommand) combined = combined_klass(Distribution()) combined.initialize_options() combined.finalize_options() combined.run() assert MockCommand.called == 2 def test_run(): assert pkg.run(sys.executable + ' --version') == 0 with pytest.raises(ValueError): pkg.run('foobarbaz') def test_ensure_existing_targets(destination_dir): local_targets = ['file1.rtf', 'sub/subfile1.rtf'] targets = [str(destination_dir.join(t)) for t in local_targets] cmd = pkg.ensure_targets(targets) run_command(cmd) def test_ensure_missing_targets(source_dir): local_targets = ['file1.rtf', 'sub/subfile1.rtf'] targets = [str(source_dir.join(t)) for t in local_targets] cmd = pkg.ensure_targets(targets) with pytest.raises(ValueError): run_command(cmd) def test_ensure_with_skip_npm(source_dir, mocker): mocker.patch('jupyter_packaging.setupbase.skip_npm', True) local_targets = ['file1.rtf', 'sub/subfile1.rtf'] targets = [str(source_dir.join(t)) for t in local_targets] cmd = pkg.ensure_targets(targets) run_command(cmd) class TestCommand(pkg.BaseCommand): def run(self): raise RuntimeError() # Prevent pytest from trying to collect TestCommand as a test: TestCommand.__test__ = False def test_skip_existing(destination_dir): local_targets = ['file1.rtf', 'sub/subfile1.rtf'] targets = [str(destination_dir.join(t)) for t in local_targets] cmd = pkg.skip_if_exists(targets, TestCommand) run_command(cmd) def test_no_skip_missing(source_dir): local_targets = ['file1.rtf', 'sub/subfile1.rtf'] targets = [str(source_dir.join(t)) for t in local_targets] cmd = pkg.skip_if_exists(targets, TestCommand) with pytest.raises(RuntimeError): run_command(cmd) jupyter-packaging-0.11.1/tests/utils.py000066400000000000000000000006011414452047400200740ustar00rootroot00000000000000 from setuptools.dist import Distribution def mock_dist(): return Distribution(dict( script_name='setup.py', packages=['foo'], name='foo', )) def run_command(cmd): """Run a distutils/setuptools Command """ dist = mock_dist() instance = cmd(dist) instance.initialize_options() instance.finalize_options() return instance.run()