diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4621eb7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + schedule: + # Monthly, 1st of the month, 6am + - cron: "0 6 1 * *" + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build twine + - name: Build sdist/wheel and check metadata + run: | + python -m build + python -m twine check --strict dist/* + + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11"] + os: [ubuntu-latest, macos-latest, windows-latest] + include: + - python-version: "3.7" + os: ubuntu-latest + name: oldest-dependencies + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install oldest dependencies + if: ${{ contains(matrix.name,'oldest') }} + run: | + python -m pip install --upgrade pip + pip install astropy~=4.0.0 numpy~=1.16.0 pyparsing~=2.0.0 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[test] + - name: Test with pytest + run: | + pytest --import-mode=importlib --pyargs pyregion diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d985c07..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "astropy_helpers"] - path = astropy_helpers - url = http://github.com/astropy/astropy-helpers.git diff --git a/.landscape.yml b/.landscape.yml deleted file mode 100644 index 9bc8e61..0000000 --- a/.landscape.yml +++ /dev/null @@ -1,12 +0,0 @@ -# This is the config file for landscape.io -# https://landscape.io/pages/docs -# https://landscape.io/github/astropy/pyregion/ - -ignore-paths: - - docs/conf.py - - pyregion/version.py - - pyregion/conftest.py - - pyregion/_astropy_init.py - - pyregion/cython_version.py - - ah_bootstrap.py - - ez_setup.py diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..bdb805b --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,26 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-20.04 + tools: + python: "3.10" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +formats: html + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bef8264..0000000 --- a/.travis.yml +++ /dev/null @@ -1,77 +0,0 @@ -language: python - -# Setting sudo to false opts in to Travis-CI container-based builds. -sudo: false - -# The apt packages below are needed for sphinx builds. -addons: - apt: - packages: - - graphviz - - texlive-latex-extra - - dvipng - -python: - - 2.7 - - 3.5 - -env: - global: - - NUMPY_VERSION=stable - - ASTROPY_VERSION=stable - - CONDA_DEPENDENCIES='matplotlib pyparsing Cython' - - SETUP_CMD='test' - - SETUP_XVFB=True - - CONDA_CHANNELS='astropy-ci-extras' - - matrix: - - SETUP_CMD='egg_info' - - SETUP_CMD='test' - -matrix: - include: - - # The main test, where we measure coverage - - python: 3.6 - env: SETUP_CMD='test --coverage' - - # Check for sphinx doc build warnings - we do this first because it - # may run for a long time - - python: 2.7 - env: SETUP_CMD='build_docs' PIP_DEPENDENCIES='sphinx_rtd_theme' - - python: 3.5 - env: SETUP_CMD='build_docs -w' PIP_DEPENDENCIES='sphinx_rtd_theme' - - # Try Astropy development and LTS version - - python: 3.6 - env: ASTROPY_VERSION=development - - python: 2.7 - env: ASTROPY_VERSION=lts - - python: 3.5 - env: ASTROPY_VERSION=lts - - # Try older numpy versions - - python: 3.4 - env: NUMPY_VERSION=1.11 - - python: 2.7 - env: NUMPY_VERSION=1.10 - - python: 2.7 - env: NUMPY_VERSION=1.9 - - python: 3.6 - env: NUMPY_VERSION=1.13 - - # Try numpy pre-release - - python: 3.5 - env: NUMPY_VERSION=prerelease - -install: - - git clone git://github.com/astropy/ci-helpers.git - - source ci-helpers/travis/setup_conda.sh - -script: - - python setup.py $SETUP_CMD - -after_success: - - if [[ $SETUP_CMD == 'test --coverage' ]]; then - coveralls --rcfile='pyregion/tests/coveragerc'; - fi diff --git a/MANIFEST.in b/MANIFEST.in index efdfe0e..b8b152e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,40 +1,13 @@ include LICENSE include README.rst include CHANGES.rst - -include ah_bootstrap.py include setup.cfg +include setup.py +include pyproject.toml recursive-include pyregion *.pyx *.c *.pxd recursive-include docs * -recursive-include licenses * -recursive-include cextern * -recursive-include scripts * prune build prune docs/_build prune docs/api - - -# the next few stanzas are for astropy_helpers. It's derived from the -# astropy_helpers/MANIFEST.in, but requires additional includes for the actual -# package directory and egg-info. - -include astropy_helpers/README.rst -include astropy_helpers/CHANGES.rst -include astropy_helpers/LICENSE.rst -recursive-include astropy_helpers/licenses * - -include astropy_helpers/ez_setup.py -include astropy_helpers/ah_bootstrap.py - -recursive-include astropy_helpers/astropy_helpers *.py *.pyx *.c *.h -recursive-include astropy_helpers/astropy_helpers.egg-info * -# include the sphinx stuff with "*" because there are css/html/rst/etc. -recursive-include astropy_helpers/astropy_helpers/sphinx * - -prune astropy_helpers/build -prune astropy_helpers/astropy_helpers/tests - - -global-exclude *.pyc *.o diff --git a/ah_bootstrap.py b/ah_bootstrap.py deleted file mode 100644 index 86db377..0000000 --- a/ah_bootstrap.py +++ /dev/null @@ -1,1023 +0,0 @@ -""" -This bootstrap module contains code for ensuring that the astropy_helpers -package will be importable by the time the setup.py script runs. It also -includes some workarounds to ensure that a recent-enough version of setuptools -is being used for the installation. - -This module should be the first thing imported in the setup.py of distributions -that make use of the utilities in astropy_helpers. If the distribution ships -with its own copy of astropy_helpers, this module will first attempt to import -from the shipped copy. However, it will also check PyPI to see if there are -any bug-fix releases on top of the current version that may be useful to get -past platform-specific bugs that have been fixed. When running setup.py, use -the ``--offline`` command-line option to disable the auto-upgrade checks. - -When this module is imported or otherwise executed it automatically calls a -main function that attempts to read the project's setup.cfg file, which it -checks for a configuration section called ``[ah_bootstrap]`` the presences of -that section, and options therein, determine the next step taken: If it -contains an option called ``auto_use`` with a value of ``True``, it will -automatically call the main function of this module called -`use_astropy_helpers` (see that function's docstring for full details). -Otherwise no further action is taken and by default the system-installed version -of astropy-helpers will be used (however, ``ah_bootstrap.use_astropy_helpers`` -may be called manually from within the setup.py script). - -This behavior can also be controlled using the ``--auto-use`` and -``--no-auto-use`` command-line flags. For clarity, an alias for -``--no-auto-use`` is ``--use-system-astropy-helpers``, and we recommend using -the latter if needed. - -Additional options in the ``[ah_boostrap]`` section of setup.cfg have the same -names as the arguments to `use_astropy_helpers`, and can be used to configure -the bootstrap script when ``auto_use = True``. - -See https://github.com/astropy/astropy-helpers for more details, and for the -latest version of this module. -""" - -import contextlib -import errno -import io -import locale -import os -import re -import subprocess as sp -import sys - -from distutils import log -from distutils.debug import DEBUG - - -try: - from ConfigParser import ConfigParser, RawConfigParser -except ImportError: - from configparser import ConfigParser, RawConfigParser - -import pkg_resources - -from setuptools import Distribution -from setuptools.package_index import PackageIndex - -# This is the minimum Python version required for astropy-helpers -__minimum_python_version__ = (2, 7) - -if sys.version_info[0] < 3: - _str_types = (str, unicode) - _text_type = unicode - PY3 = False -else: - _str_types = (str, bytes) - _text_type = str - PY3 = True - -# TODO: Maybe enable checking for a specific version of astropy_helpers? -DIST_NAME = 'astropy-helpers' -PACKAGE_NAME = 'astropy_helpers' - -if PY3: - UPPER_VERSION_EXCLUSIVE = None -else: - UPPER_VERSION_EXCLUSIVE = '3' - -# Defaults for other options -DOWNLOAD_IF_NEEDED = True -INDEX_URL = 'https://pypi.python.org/simple' -USE_GIT = True -OFFLINE = False -AUTO_UPGRADE = True - -# A list of all the configuration options and their required types -CFG_OPTIONS = [ - ('auto_use', bool), ('path', str), ('download_if_needed', bool), - ('index_url', str), ('use_git', bool), ('offline', bool), - ('auto_upgrade', bool) -] - -# Start off by parsing the setup.cfg file - -SETUP_CFG = ConfigParser() - -if os.path.exists('setup.cfg'): - - try: - SETUP_CFG.read('setup.cfg') - except Exception as e: - if DEBUG: - raise - - log.error( - "Error reading setup.cfg: {0!r}\n{1} will not be " - "automatically bootstrapped and package installation may fail." - "\n{2}".format(e, PACKAGE_NAME, _err_help_msg)) - -# We used package_name in the package template for a while instead of name -if SETUP_CFG.has_option('metadata', 'name'): - parent_package = SETUP_CFG.get('metadata', 'name') -elif SETUP_CFG.has_option('metadata', 'package_name'): - parent_package = SETUP_CFG.get('metadata', 'package_name') -else: - parent_package = None - -if SETUP_CFG.has_option('options', 'python_requires'): - - python_requires = SETUP_CFG.get('options', 'python_requires') - - # The python_requires key has a syntax that can be parsed by SpecifierSet - # in the packaging package. However, we don't want to have to depend on that - # package, so instead we can use setuptools (which bundles packaging). We - # have to add 'python' to parse it with Requirement. - - from pkg_resources import Requirement - req = Requirement.parse('python' + python_requires) - - # We want the Python version as a string, which we can get from the platform module - import platform - # strip off trailing '+' incase this is a dev install of python - python_version = platform.python_version().strip('+') - # allow pre-releases to count as 'new enough' - if not req.specifier.contains(python_version, True): - if parent_package is None: - print("ERROR: Python {} is required by this package".format(req.specifier)) - else: - print("ERROR: Python {} is required by {}".format(req.specifier, parent_package)) - sys.exit(1) - -if sys.version_info < __minimum_python_version__: - - if parent_package is None: - print("ERROR: Python {} or later is required by astropy-helpers".format( - __minimum_python_version__)) - else: - print("ERROR: Python {} or later is required by astropy-helpers for {}".format( - __minimum_python_version__, parent_package)) - - sys.exit(1) - - -# What follows are several import statements meant to deal with install-time -# issues with either missing or misbehaving pacakges (including making sure -# setuptools itself is installed): - -# Check that setuptools 1.0 or later is present -from distutils.version import LooseVersion - -try: - import setuptools - assert LooseVersion(setuptools.__version__) >= LooseVersion('1.0') -except (ImportError, AssertionError): - print("ERROR: setuptools 1.0 or later is required by astropy-helpers") - sys.exit(1) - -# typing as a dependency for 1.6.1+ Sphinx causes issues when imported after -# initializing submodule with ah_boostrap.py -# See discussion and references in -# https://github.com/astropy/astropy-helpers/issues/302 - -try: - import typing # noqa -except ImportError: - pass - - -# Note: The following import is required as a workaround to -# https://github.com/astropy/astropy-helpers/issues/89; if we don't import this -# module now, it will get cleaned up after `run_setup` is called, but that will -# later cause the TemporaryDirectory class defined in it to stop working when -# used later on by setuptools -try: - import setuptools.py31compat # noqa -except ImportError: - pass - - -# matplotlib can cause problems if it is imported from within a call of -# run_setup(), because in some circumstances it will try to write to the user's -# home directory, resulting in a SandboxViolation. See -# https://github.com/matplotlib/matplotlib/pull/4165 -# Making sure matplotlib, if it is available, is imported early in the setup -# process can mitigate this (note importing matplotlib.pyplot has the same -# issue) -try: - import matplotlib - matplotlib.use('Agg') - import matplotlib.pyplot -except: - # Ignore if this fails for *any* reason* - pass - - -# End compatibility imports... - - -class _Bootstrapper(object): - """ - Bootstrapper implementation. See ``use_astropy_helpers`` for parameter - documentation. - """ - - def __init__(self, path=None, index_url=None, use_git=None, offline=None, - download_if_needed=None, auto_upgrade=None): - - if path is None: - path = PACKAGE_NAME - - if not (isinstance(path, _str_types) or path is False): - raise TypeError('path must be a string or False') - - if PY3 and not isinstance(path, _text_type): - fs_encoding = sys.getfilesystemencoding() - path = path.decode(fs_encoding) # path to unicode - - self.path = path - - # Set other option attributes, using defaults where necessary - self.index_url = index_url if index_url is not None else INDEX_URL - self.offline = offline if offline is not None else OFFLINE - - # If offline=True, override download and auto-upgrade - if self.offline: - download_if_needed = False - auto_upgrade = False - - self.download = (download_if_needed - if download_if_needed is not None - else DOWNLOAD_IF_NEEDED) - self.auto_upgrade = (auto_upgrade - if auto_upgrade is not None else AUTO_UPGRADE) - - # If this is a release then the .git directory will not exist so we - # should not use git. - git_dir_exists = os.path.exists(os.path.join(os.path.dirname(__file__), '.git')) - if use_git is None and not git_dir_exists: - use_git = False - - self.use_git = use_git if use_git is not None else USE_GIT - # Declared as False by default--later we check if astropy-helpers can be - # upgraded from PyPI, but only if not using a source distribution (as in - # the case of import from a git submodule) - self.is_submodule = False - - @classmethod - def main(cls, argv=None): - if argv is None: - argv = sys.argv - - config = cls.parse_config() - config.update(cls.parse_command_line(argv)) - - auto_use = config.pop('auto_use', False) - bootstrapper = cls(**config) - - if auto_use: - # Run the bootstrapper, otherwise the setup.py is using the old - # use_astropy_helpers() interface, in which case it will run the - # bootstrapper manually after reconfiguring it. - bootstrapper.run() - - return bootstrapper - - @classmethod - def parse_config(cls): - - if not SETUP_CFG.has_section('ah_bootstrap'): - return {} - - config = {} - - for option, type_ in CFG_OPTIONS: - if not SETUP_CFG.has_option('ah_bootstrap', option): - continue - - if type_ is bool: - value = SETUP_CFG.getboolean('ah_bootstrap', option) - else: - value = SETUP_CFG.get('ah_bootstrap', option) - - config[option] = value - - return config - - @classmethod - def parse_command_line(cls, argv=None): - if argv is None: - argv = sys.argv - - config = {} - - # For now we just pop recognized ah_bootstrap options out of the - # arg list. This is imperfect; in the unlikely case that a setup.py - # custom command or even custom Distribution class defines an argument - # of the same name then we will break that. However there's a catch22 - # here that we can't just do full argument parsing right here, because - # we don't yet know *how* to parse all possible command-line arguments. - if '--no-git' in argv: - config['use_git'] = False - argv.remove('--no-git') - - if '--offline' in argv: - config['offline'] = True - argv.remove('--offline') - - if '--auto-use' in argv: - config['auto_use'] = True - argv.remove('--auto-use') - - if '--no-auto-use' in argv: - config['auto_use'] = False - argv.remove('--no-auto-use') - - if '--use-system-astropy-helpers' in argv: - config['auto_use'] = False - argv.remove('--use-system-astropy-helpers') - - return config - - def run(self): - strategies = ['local_directory', 'local_file', 'index'] - dist = None - - # First, remove any previously imported versions of astropy_helpers; - # this is necessary for nested installs where one package's installer - # is installing another package via setuptools.sandbox.run_setup, as in - # the case of setup_requires - for key in list(sys.modules): - try: - if key == PACKAGE_NAME or key.startswith(PACKAGE_NAME + '.'): - del sys.modules[key] - except AttributeError: - # Sometimes mysterious non-string things can turn up in - # sys.modules - continue - - # Check to see if the path is a submodule - self.is_submodule = self._check_submodule() - - for strategy in strategies: - method = getattr(self, 'get_{0}_dist'.format(strategy)) - dist = method() - if dist is not None: - break - else: - raise _AHBootstrapSystemExit( - "No source found for the {0!r} package; {0} must be " - "available and importable as a prerequisite to building " - "or installing this package.".format(PACKAGE_NAME)) - - # This is a bit hacky, but if astropy_helpers was loaded from a - # directory/submodule its Distribution object gets a "precedence" of - # "DEVELOP_DIST". However, in other cases it gets a precedence of - # "EGG_DIST". However, when activing the distribution it will only be - # placed early on sys.path if it is treated as an EGG_DIST, so always - # do that - dist = dist.clone(precedence=pkg_resources.EGG_DIST) - - # Otherwise we found a version of astropy-helpers, so we're done - # Just active the found distribution on sys.path--if we did a - # download this usually happens automatically but it doesn't hurt to - # do it again - # Note: Adding the dist to the global working set also activates it - # (makes it importable on sys.path) by default. - - try: - pkg_resources.working_set.add(dist, replace=True) - except TypeError: - # Some (much) older versions of setuptools do not have the - # replace=True option here. These versions are old enough that all - # bets may be off anyways, but it's easy enough to work around just - # in case... - if dist.key in pkg_resources.working_set.by_key: - del pkg_resources.working_set.by_key[dist.key] - pkg_resources.working_set.add(dist) - - @property - def config(self): - """ - A `dict` containing the options this `_Bootstrapper` was configured - with. - """ - - return dict((optname, getattr(self, optname)) - for optname, _ in CFG_OPTIONS if hasattr(self, optname)) - - def get_local_directory_dist(self): - """ - Handle importing a vendored package from a subdirectory of the source - distribution. - """ - - if not os.path.isdir(self.path): - return - - log.info('Attempting to import astropy_helpers from {0} {1!r}'.format( - 'submodule' if self.is_submodule else 'directory', - self.path)) - - dist = self._directory_import() - - if dist is None: - log.warn( - 'The requested path {0!r} for importing {1} does not ' - 'exist, or does not contain a copy of the {1} ' - 'package.'.format(self.path, PACKAGE_NAME)) - elif self.auto_upgrade and not self.is_submodule: - # A version of astropy-helpers was found on the available path, but - # check to see if a bugfix release is available on PyPI - upgrade = self._do_upgrade(dist) - if upgrade is not None: - dist = upgrade - - return dist - - def get_local_file_dist(self): - """ - Handle importing from a source archive; this also uses setup_requires - but points easy_install directly to the source archive. - """ - - if not os.path.isfile(self.path): - return - - log.info('Attempting to unpack and import astropy_helpers from ' - '{0!r}'.format(self.path)) - - try: - dist = self._do_download(find_links=[self.path]) - except Exception as e: - if DEBUG: - raise - - log.warn( - 'Failed to import {0} from the specified archive {1!r}: ' - '{2}'.format(PACKAGE_NAME, self.path, str(e))) - dist = None - - if dist is not None and self.auto_upgrade: - # A version of astropy-helpers was found on the available path, but - # check to see if a bugfix release is available on PyPI - upgrade = self._do_upgrade(dist) - if upgrade is not None: - dist = upgrade - - return dist - - def get_index_dist(self): - if not self.download: - log.warn('Downloading {0!r} disabled.'.format(DIST_NAME)) - return None - - log.warn( - "Downloading {0!r}; run setup.py with the --offline option to " - "force offline installation.".format(DIST_NAME)) - - try: - dist = self._do_download() - except Exception as e: - if DEBUG: - raise - log.warn( - 'Failed to download and/or install {0!r} from {1!r}:\n' - '{2}'.format(DIST_NAME, self.index_url, str(e))) - dist = None - - # No need to run auto-upgrade here since we've already presumably - # gotten the most up-to-date version from the package index - return dist - - def _directory_import(self): - """ - Import astropy_helpers from the given path, which will be added to - sys.path. - - Must return True if the import succeeded, and False otherwise. - """ - - # Return True on success, False on failure but download is allowed, and - # otherwise raise SystemExit - path = os.path.abspath(self.path) - - # Use an empty WorkingSet rather than the man - # pkg_resources.working_set, since on older versions of setuptools this - # will invoke a VersionConflict when trying to install an upgrade - ws = pkg_resources.WorkingSet([]) - ws.add_entry(path) - dist = ws.by_key.get(DIST_NAME) - - if dist is None: - # We didn't find an egg-info/dist-info in the given path, but if a - # setup.py exists we can generate it - setup_py = os.path.join(path, 'setup.py') - if os.path.isfile(setup_py): - # We use subprocess instead of run_setup from setuptools to - # avoid segmentation faults - see the following for more details: - # https://github.com/cython/cython/issues/2104 - sp.check_output([sys.executable, 'setup.py', 'egg_info'], cwd=path) - - for dist in pkg_resources.find_distributions(path, True): - # There should be only one... - return dist - - return dist - - def _do_download(self, version='', find_links=None): - if find_links: - allow_hosts = '' - index_url = None - else: - allow_hosts = None - index_url = self.index_url - - # Annoyingly, setuptools will not handle other arguments to - # Distribution (such as options) before handling setup_requires, so it - # is not straightforward to programmatically augment the arguments which - # are passed to easy_install - class _Distribution(Distribution): - def get_option_dict(self, command_name): - opts = Distribution.get_option_dict(self, command_name) - if command_name == 'easy_install': - if find_links is not None: - opts['find_links'] = ('setup script', find_links) - if index_url is not None: - opts['index_url'] = ('setup script', index_url) - if allow_hosts is not None: - opts['allow_hosts'] = ('setup script', allow_hosts) - return opts - - if version: - req = '{0}=={1}'.format(DIST_NAME, version) - else: - if UPPER_VERSION_EXCLUSIVE is None: - req = DIST_NAME - else: - req = '{0}<{1}'.format(DIST_NAME, UPPER_VERSION_EXCLUSIVE) - - attrs = {'setup_requires': [req]} - - # NOTE: we need to parse the config file (e.g. setup.cfg) to make sure - # it honours the options set in the [easy_install] section, and we need - # to explicitly fetch the requirement eggs as setup_requires does not - # get honored in recent versions of setuptools: - # https://github.com/pypa/setuptools/issues/1273 - - try: - - context = _verbose if DEBUG else _silence - with context(): - dist = _Distribution(attrs=attrs) - try: - dist.parse_config_files(ignore_option_errors=True) - dist.fetch_build_eggs(req) - except TypeError: - # On older versions of setuptools, ignore_option_errors - # doesn't exist, and the above two lines are not needed - # so we can just continue - pass - - # If the setup_requires succeeded it will have added the new dist to - # the main working_set - return pkg_resources.working_set.by_key.get(DIST_NAME) - except Exception as e: - if DEBUG: - raise - - msg = 'Error retrieving {0} from {1}:\n{2}' - if find_links: - source = find_links[0] - elif index_url != INDEX_URL: - source = index_url - else: - source = 'PyPI' - - raise Exception(msg.format(DIST_NAME, source, repr(e))) - - def _do_upgrade(self, dist): - # Build up a requirement for a higher bugfix release but a lower minor - # release (so API compatibility is guaranteed) - next_version = _next_version(dist.parsed_version) - - req = pkg_resources.Requirement.parse( - '{0}>{1},<{2}'.format(DIST_NAME, dist.version, next_version)) - - package_index = PackageIndex(index_url=self.index_url) - - upgrade = package_index.obtain(req) - - if upgrade is not None: - return self._do_download(version=upgrade.version) - - def _check_submodule(self): - """ - Check if the given path is a git submodule. - - See the docstrings for ``_check_submodule_using_git`` and - ``_check_submodule_no_git`` for further details. - """ - - if (self.path is None or - (os.path.exists(self.path) and not os.path.isdir(self.path))): - return False - - if self.use_git: - return self._check_submodule_using_git() - else: - return self._check_submodule_no_git() - - def _check_submodule_using_git(self): - """ - Check if the given path is a git submodule. If so, attempt to initialize - and/or update the submodule if needed. - - This function makes calls to the ``git`` command in subprocesses. The - ``_check_submodule_no_git`` option uses pure Python to check if the given - path looks like a git submodule, but it cannot perform updates. - """ - - cmd = ['git', 'submodule', 'status', '--', self.path] - - try: - log.info('Running `{0}`; use the --no-git option to disable git ' - 'commands'.format(' '.join(cmd))) - returncode, stdout, stderr = run_cmd(cmd) - except _CommandNotFound: - # The git command simply wasn't found; this is most likely the - # case on user systems that don't have git and are simply - # trying to install the package from PyPI or a source - # distribution. Silently ignore this case and simply don't try - # to use submodules - return False - - stderr = stderr.strip() - - if returncode != 0 and stderr: - # Unfortunately the return code alone cannot be relied on, as - # earlier versions of git returned 0 even if the requested submodule - # does not exist - - # This is a warning that occurs in perl (from running git submodule) - # which only occurs with a malformatted locale setting which can - # happen sometimes on OSX. See again - # https://github.com/astropy/astropy/issues/2749 - perl_warning = ('perl: warning: Falling back to the standard locale ' - '("C").') - if not stderr.strip().endswith(perl_warning): - # Some other unknown error condition occurred - log.warn('git submodule command failed ' - 'unexpectedly:\n{0}'.format(stderr)) - return False - - # Output of `git submodule status` is as follows: - # - # 1: Status indicator: '-' for submodule is uninitialized, '+' if - # submodule is initialized but is not at the commit currently indicated - # in .gitmodules (and thus needs to be updated), or 'U' if the - # submodule is in an unstable state (i.e. has merge conflicts) - # - # 2. SHA-1 hash of the current commit of the submodule (we don't really - # need this information but it's useful for checking that the output is - # correct) - # - # 3. The output of `git describe` for the submodule's current commit - # hash (this includes for example what branches the commit is on) but - # only if the submodule is initialized. We ignore this information for - # now - _git_submodule_status_re = re.compile( - r'^(?P[+-U ])(?P[0-9a-f]{40}) ' - r'(?P\S+)( .*)?$') - - # The stdout should only contain one line--the status of the - # requested submodule - m = _git_submodule_status_re.match(stdout) - if m: - # Yes, the path *is* a git submodule - self._update_submodule(m.group('submodule'), m.group('status')) - return True - else: - log.warn( - 'Unexpected output from `git submodule status`:\n{0}\n' - 'Will attempt import from {1!r} regardless.'.format( - stdout, self.path)) - return False - - def _check_submodule_no_git(self): - """ - Like ``_check_submodule_using_git``, but simply parses the .gitmodules file - to determine if the supplied path is a git submodule, and does not exec any - subprocesses. - - This can only determine if a path is a submodule--it does not perform - updates, etc. This function may need to be updated if the format of the - .gitmodules file is changed between git versions. - """ - - gitmodules_path = os.path.abspath('.gitmodules') - - if not os.path.isfile(gitmodules_path): - return False - - # This is a minimal reader for gitconfig-style files. It handles a few of - # the quirks that make gitconfig files incompatible with ConfigParser-style - # files, but does not support the full gitconfig syntax (just enough - # needed to read a .gitmodules file). - gitmodules_fileobj = io.StringIO() - - # Must use io.open for cross-Python-compatible behavior wrt unicode - with io.open(gitmodules_path) as f: - for line in f: - # gitconfig files are more flexible with leading whitespace; just - # go ahead and remove it - line = line.lstrip() - - # comments can start with either # or ; - if line and line[0] in (':', ';'): - continue - - gitmodules_fileobj.write(line) - - gitmodules_fileobj.seek(0) - - cfg = RawConfigParser() - - try: - cfg.readfp(gitmodules_fileobj) - except Exception as exc: - log.warn('Malformatted .gitmodules file: {0}\n' - '{1} cannot be assumed to be a git submodule.'.format( - exc, self.path)) - return False - - for section in cfg.sections(): - if not cfg.has_option(section, 'path'): - continue - - submodule_path = cfg.get(section, 'path').rstrip(os.sep) - - if submodule_path == self.path.rstrip(os.sep): - return True - - return False - - def _update_submodule(self, submodule, status): - if status == ' ': - # The submodule is up to date; no action necessary - return - elif status == '-': - if self.offline: - raise _AHBootstrapSystemExit( - "Cannot initialize the {0} submodule in --offline mode; " - "this requires being able to clone the submodule from an " - "online repository.".format(submodule)) - cmd = ['update', '--init'] - action = 'Initializing' - elif status == '+': - cmd = ['update'] - action = 'Updating' - if self.offline: - cmd.append('--no-fetch') - elif status == 'U': - raise _AHBootstrapSystemExit( - 'Error: Submodule {0} contains unresolved merge conflicts. ' - 'Please complete or abandon any changes in the submodule so that ' - 'it is in a usable state, then try again.'.format(submodule)) - else: - log.warn('Unknown status {0!r} for git submodule {1!r}. Will ' - 'attempt to use the submodule as-is, but try to ensure ' - 'that the submodule is in a clean state and contains no ' - 'conflicts or errors.\n{2}'.format(status, submodule, - _err_help_msg)) - return - - err_msg = None - cmd = ['git', 'submodule'] + cmd + ['--', submodule] - log.warn('{0} {1} submodule with: `{2}`'.format( - action, submodule, ' '.join(cmd))) - - try: - log.info('Running `{0}`; use the --no-git option to disable git ' - 'commands'.format(' '.join(cmd))) - returncode, stdout, stderr = run_cmd(cmd) - except OSError as e: - err_msg = str(e) - else: - if returncode != 0: - err_msg = stderr - - if err_msg is not None: - log.warn('An unexpected error occurred updating the git submodule ' - '{0!r}:\n{1}\n{2}'.format(submodule, err_msg, - _err_help_msg)) - -class _CommandNotFound(OSError): - """ - An exception raised when a command run with run_cmd is not found on the - system. - """ - - -def run_cmd(cmd): - """ - Run a command in a subprocess, given as a list of command-line - arguments. - - Returns a ``(returncode, stdout, stderr)`` tuple. - """ - - try: - p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) - # XXX: May block if either stdout or stderr fill their buffers; - # however for the commands this is currently used for that is - # unlikely (they should have very brief output) - stdout, stderr = p.communicate() - except OSError as e: - if DEBUG: - raise - - if e.errno == errno.ENOENT: - msg = 'Command not found: `{0}`'.format(' '.join(cmd)) - raise _CommandNotFound(msg, cmd) - else: - raise _AHBootstrapSystemExit( - 'An unexpected error occurred when running the ' - '`{0}` command:\n{1}'.format(' '.join(cmd), str(e))) - - - # Can fail of the default locale is not configured properly. See - # https://github.com/astropy/astropy/issues/2749. For the purposes under - # consideration 'latin1' is an acceptable fallback. - try: - stdio_encoding = locale.getdefaultlocale()[1] or 'latin1' - except ValueError: - # Due to an OSX oddity locale.getdefaultlocale() can also crash - # depending on the user's locale/language settings. See: - # http://bugs.python.org/issue18378 - stdio_encoding = 'latin1' - - # Unlikely to fail at this point but even then let's be flexible - if not isinstance(stdout, _text_type): - stdout = stdout.decode(stdio_encoding, 'replace') - if not isinstance(stderr, _text_type): - stderr = stderr.decode(stdio_encoding, 'replace') - - return (p.returncode, stdout, stderr) - - -def _next_version(version): - """ - Given a parsed version from pkg_resources.parse_version, returns a new - version string with the next minor version. - - Examples - ======== - >>> _next_version(pkg_resources.parse_version('1.2.3')) - '1.3.0' - """ - - if hasattr(version, 'base_version'): - # New version parsing from setuptools >= 8.0 - if version.base_version: - parts = version.base_version.split('.') - else: - parts = [] - else: - parts = [] - for part in version: - if part.startswith('*'): - break - parts.append(part) - - parts = [int(p) for p in parts] - - if len(parts) < 3: - parts += [0] * (3 - len(parts)) - - major, minor, micro = parts[:3] - - return '{0}.{1}.{2}'.format(major, minor + 1, 0) - - -class _DummyFile(object): - """A noop writeable object.""" - - errors = '' # Required for Python 3.x - encoding = 'utf-8' - - def write(self, s): - pass - - def flush(self): - pass - - -@contextlib.contextmanager -def _verbose(): - yield - -@contextlib.contextmanager -def _silence(): - """A context manager that silences sys.stdout and sys.stderr.""" - - old_stdout = sys.stdout - old_stderr = sys.stderr - sys.stdout = _DummyFile() - sys.stderr = _DummyFile() - exception_occurred = False - try: - yield - except: - exception_occurred = True - # Go ahead and clean up so that exception handling can work normally - sys.stdout = old_stdout - sys.stderr = old_stderr - raise - - if not exception_occurred: - sys.stdout = old_stdout - sys.stderr = old_stderr - - -_err_help_msg = """ -If the problem persists consider installing astropy_helpers manually using pip -(`pip install astropy_helpers`) or by manually downloading the source archive, -extracting it, and installing by running `python setup.py install` from the -root of the extracted source code. -""" - - -class _AHBootstrapSystemExit(SystemExit): - def __init__(self, *args): - if not args: - msg = 'An unknown problem occurred bootstrapping astropy_helpers.' - else: - msg = args[0] - - msg += '\n' + _err_help_msg - - super(_AHBootstrapSystemExit, self).__init__(msg, *args[1:]) - - -BOOTSTRAPPER = _Bootstrapper.main() - - -def use_astropy_helpers(**kwargs): - """ - Ensure that the `astropy_helpers` module is available and is importable. - This supports automatic submodule initialization if astropy_helpers is - included in a project as a git submodule, or will download it from PyPI if - necessary. - - Parameters - ---------- - - path : str or None, optional - A filesystem path relative to the root of the project's source code - that should be added to `sys.path` so that `astropy_helpers` can be - imported from that path. - - If the path is a git submodule it will automatically be initialized - and/or updated. - - The path may also be to a ``.tar.gz`` archive of the astropy_helpers - source distribution. In this case the archive is automatically - unpacked and made temporarily available on `sys.path` as a ``.egg`` - archive. - - If `None` skip straight to downloading. - - download_if_needed : bool, optional - If the provided filesystem path is not found an attempt will be made to - download astropy_helpers from PyPI. It will then be made temporarily - available on `sys.path` as a ``.egg`` archive (using the - ``setup_requires`` feature of setuptools. If the ``--offline`` option - is given at the command line the value of this argument is overridden - to `False`. - - index_url : str, optional - If provided, use a different URL for the Python package index than the - main PyPI server. - - use_git : bool, optional - If `False` no git commands will be used--this effectively disables - support for git submodules. If the ``--no-git`` option is given at the - command line the value of this argument is overridden to `False`. - - auto_upgrade : bool, optional - By default, when installing a package from a non-development source - distribution ah_boostrap will try to automatically check for patch - releases to astropy-helpers on PyPI and use the patched version over - any bundled versions. Setting this to `False` will disable that - functionality. If the ``--offline`` option is given at the command line - the value of this argument is overridden to `False`. - - offline : bool, optional - If `False` disable all actions that require an internet connection, - including downloading packages from the package index and fetching - updates to any git submodule. Defaults to `True`. - """ - - global BOOTSTRAPPER - - config = BOOTSTRAPPER.config - config.update(**kwargs) - - # Create a new bootstrapper with the updated configuration and run it - BOOTSTRAPPER = _Bootstrapper(**config) - BOOTSTRAPPER.run() diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index a0fa52a..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,44 +0,0 @@ -# AppVeyor.com is a Continuous Integration service to build and run tests under -# Windows - -environment: - - global: - PYTHON: "C:\\conda" - MINICONDA_VERSION: "latest" - CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci-helpers\\appveyor\\windows_sdk.cmd" - PYTHON_ARCH: "64" # needs to be set for CMD_IN_ENV to succeed. If a mix - # of 32 bit and 64 bit builds are needed, move this - # to the matrix section. - # For this package-template, we include examples of Cython modules, - # so Cython is required for testing. If your package does not include - # Cython code, you can set CONDA_DEPENDENCIES='' - CONDA_DEPENDENCIES: "Cython matplotlib pyparsing" - - matrix: - - # We test Python 2.7 and 3.5 because 2.7 is the supported Python 2 - # release of Astropy and Python 3.5 is the latest Python 3 release. - - - PYTHON_VERSION: "2.7" - ASTROPY_VERSION: "stable" - NUMPY_VERSION: "stable" - - - PYTHON_VERSION: "3.5" - ASTROPY_VERSION: "stable" - NUMPY_VERSION: "stable" - -platform: - -x64 - -install: - - "git clone git://github.com/astropy/ci-helpers.git" - - "powershell ci-helpers/appveyor/install-miniconda.ps1" - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - "activate test" - -# Not a .NET project, we build the package in the install step instead -build: false - -test_script: - - "%CMD_IN_ENV% python setup.py test" diff --git a/astropy_helpers b/astropy_helpers deleted file mode 160000 index fd80be7..0000000 --- a/astropy_helpers +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fd80be7cf8698e2350a919ef7fc89871db0ba580 diff --git a/docs/conf.py b/docs/conf.py index f222ce5..2785893 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,186 +1,123 @@ +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Licensed under a 3-clause BSD style license - see LICENSE.rst # -# Astropy documentation build configuration file. +# Astropy Package Template documentation build configuration file, created by +# sphinx-quickstart on Wed Jan 11 11:09:48 2017. # -# This file is execfile()d with the current directory set to its containing dir. +# This file is execfile()d with the current directory set to its +# containing dir. # -# Note that not all possible configuration values are present in this file. +# Note that not all possible configuration values are present in this +# autogenerated file. # -# All configuration values have a default. Some values are defined in -# the global Astropy configuration which is loaded here before anything else. -# See astropy.sphinx.conf for which values are set there. +# All configuration values have a default; values that are commented out +# serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('..')) -# IMPORTANT: the above commented section was generated by sphinx-quickstart, but -# is *NOT* appropriate for astropy or Astropy affiliated packages. It is left -# commented out with this explanation to make it clear why this should not be -# done. If the sys.path entry above is added, when the astropy.sphinx.conf -# import occurs, it will import the *source* version of astropy instead of the -# version installed (if invoked as "make html" or directly with sphinx), or the -# version in the build directory (if "python setup.py build_sphinx" is used). -# Thus, any C-extensions that are needed to build the documentation will *not* -# be accessible, and the documentation will not build correctly. - -import datetime -import os -import sys - -try: - import astropy_helpers -except ImportError: - # Building from inside the docs/ directory? - if os.path.basename(os.getcwd()) == 'docs': - a_h_path = os.path.abspath(os.path.join('..', 'astropy_helpers')) - if os.path.isdir(a_h_path): - sys.path.insert(1, a_h_path) - -# Load all of the global Astropy configuration -from astropy_helpers.sphinx.conf import * - -# Get configuration information from setup.cfg -try: - from ConfigParser import ConfigParser -except ImportError: - from configparser import ConfigParser -conf = ConfigParser() - -conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')]) -setup_cfg = dict(conf.items('metadata')) - -# -- General configuration ---------------------------------------------------- - -# Remove intersphinx mappings we don't need (for faster docs build) -del intersphinx_mapping['h5py'] -del intersphinx_mapping['scipy'] +# -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.2' - -# To perform a Sphinx version check that needs to be more specific than -# major.minor, call `check_sphinx_version("x.y.z")` here. -# check_sphinx_version("1.2.1") - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns.append('_templates') - -# This is added to the end of RST files - a good place to put substitutions to -# be used globally. -rst_epilog += """ -""" - -# -- Project information ------------------------------------------------------ - -# This does not *have* to match the package name, but typically does -project = setup_cfg['package_name'] -author = setup_cfg['author'] -copyright = '{0}, {1}'.format( - datetime.datetime.now().year, setup_cfg['author']) +# +needs_sphinx = '1.2' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.intersphinx', + 'matplotlib.sphinxext.plot_directive', + 'sphinx.ext.autodoc', + 'sphinx_automodapi.automodapi', + 'sphinx_automodapi.automodsumm', + 'sphinx_automodapi.autodoc_enhancements', + 'sphinx_automodapi.smart_resolver', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Pyregion' +copyright = '2022, Jae-Joon Lee' +author = 'Jae-Joon Lee' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. - -__import__(setup_cfg['package_name']) -package = sys.modules[setup_cfg['package_name']] - +# # The short X.Y version. -version = package.__version__.split('-', 1)[0] +from pyregion import __version__ as version # The full version, including alpha/beta/rc tags. -release = package.__version__ - +release = version -# -- Options for HTML output -------------------------------------------------- - -# A NOTE ON HTML THEMES -# The global astropy configuration uses a custom theme, 'bootstrap-astropy', -# which is installed along with astropy. A different theme can be used or -# the options for this theme can be modified by overriding some of the -# variables set in the global configuration. The variables set in the -# global configuration are listed below, commented out. - -# Add any paths that contain custom themes here, relative to this directory. -# To use a different custom theme, add the directory containing the theme. -html_theme_path = [] - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. To override the custom theme, set this to the -# name of a builtin theme or the name of a custom theme in html_theme_path. -html_theme = "default" - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = '' - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '' - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = '{0} v{1}'.format(project, release) - -# Output file base name for HTML help builder. -htmlhelp_basename = project + 'doc' - - -# -- Options for LaTeX output ------------------------------------------------- - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [('index', project + '.tex', project + u' Documentation', - author, 'manual')] +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] -# -- Options for manual page output ------------------------------------------- +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [('index', project.lower(), project + u' Documentation', - [author], 1)] +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False +rst_epilog = """ +.. _Astropy: http://astropy.org +""" +# -- Options for HTML output ---------------------------------------------- -# -- Options for the edit_on_github extension --------------------------------- +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. -if eval(setup_cfg.get('edit_on_github')): - extensions += ['astropy_helpers.sphinx.ext.edit_on_github'] +import sphinx_rtd_theme +html_theme = 'sphinx_rtd_theme' +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - versionmod = __import__(setup_cfg['package_name'] + '.version') - edit_on_github_project = setup_cfg['github_project'] - if versionmod.version.release: - edit_on_github_branch = "v" + versionmod.version.version - else: - edit_on_github_branch = "master" +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} - edit_on_github_source_root = "" - edit_on_github_doc_root = "docs" +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] -# on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +html_sidebars = { + '**': ['localtoc.html'], + 'search': [], + 'genindex': [], + 'py-modindex': [], +} -if not on_rtd: # only import and set the theme if we're building docs locally - try: - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - except ImportError: - warnings.warn('WARNING: Readthedocs theme package ("sphinx_rtd_theme")' - ' not found, using default theme instead.') +# -- Options for HTMLHelp output ------------------------------------------ -# otherwise, readthedocs.org uses their theme by default, so no need to specify it +# Output file base name for HTML help builder. +htmlhelp_basename = 'AstropyPackageTemplatedoc' -# -- Resolving issue number to links in changelog ----------------------------- -github_issues_url = 'https://github.com/{0}/issues/'.format(setup_cfg['github_project']) +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None, + 'http://docs.astropy.org/en/stable/': None} +intersphinx_mapping["sphinx_automodapi"] = ("https://sphinx-automodapi.readthedocs.io/en/stable/", None) # noqa: E501, F405 # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- nitpicky = True diff --git a/docs/installation.rst b/docs/installation.rst index a8fd200..695461e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -16,13 +16,7 @@ To install pyregion with `pip `_ from `PyPI `_ simply run:: - pip install --no-deps pyregion - -.. note:: - - The ``--no-deps`` flag is optional, but highly recommended if you already - have Numpy installed, since otherwise pip will sometimes try to "help" you - by upgrading your Numpy installation, which may not always be desired. + pip install pyregion Using conda ----------- @@ -37,11 +31,12 @@ simply run:: Testing installation -------------------- -To check if your install is OK, run the tests: +To check if your install is OK, install the test dependencies and run the tests: .. code-block:: bash - python -c 'import pyregion; pyregion.test()' + pip install "pyregion[test]" + pytest --pyargs pyregion Development version =================== @@ -52,18 +47,19 @@ Install the latest development version from https://github.com/astropy/pyregion git clone https://github.com/astropy/pyregion cd pyregion - python setup.py install - python setup.py test - python setup.py build_docs + pip install -e .[test] + pytest + cd docs + make html Dependencies ============ -Python 2.7 and 3.4+ are supported. +Python 3.7+ is supported. ``pyregion`` has the following required dependencies: -* `Astropy `__ version 1.0 or later (which requires Numpy) +* `Astropy `__ version 4.0 or later (which requires Numpy) * ``pyparsing`` version 2.0 or later for parsing the DS9 region files * `Homepage `__ * `PyPI page `__ @@ -72,10 +68,5 @@ Python 2.7 and 3.4+ are supported. * `matplotlib `__ -If you are using Astropy version 1.3 or later, -then you have ``astropy.visualization.wcsaxes``. -For older versions of Astropy, you have to install the separate package: -`wcsaxes `__ - -To work with the development version, you'll need Cython and a C compiler, +To work with the development version, you'll need a C compiler, because the code to generate masks from regions is written in Cython. diff --git a/docs/rtd-pip-requirements b/docs/rtd-pip-requirements deleted file mode 100644 index 334d4e3..0000000 --- a/docs/rtd-pip-requirements +++ /dev/null @@ -1,5 +0,0 @@ -numpy -matplotlib -Cython -astropy-helpers -astropy diff --git a/examples/demo_region03.py b/examples/demo_region03.py index 99d5e51..977677c 100644 --- a/examples/demo_region03.py +++ b/examples/demo_region03.py @@ -1,5 +1,5 @@ import matplotlib.pyplot as plt -from mpl_toolkits.axes_grid.anchored_artists import AnchoredText +from matplotlib.offsetbox import AnchoredText from astropy.io.fits import Header from astropy.wcs import WCS from astropy.visualization.wcsaxes import WCSAxes diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d25a260 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[build-system] +requires = ["setuptools>=43.0.0", "setuptools_scm[toml]>=6.2", "wheel", + "oldest-supported-numpy", "cython"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +write_to = "pyregion/version.py" diff --git a/pyregion/__init__.py b/pyregion/__init__.py index 882da87..2c9dddf 100644 --- a/pyregion/__init__.py +++ b/pyregion/__init__.py @@ -9,12 +9,7 @@ a new astronomy package for regions based on Astropy. """ -# Affiliated packages may add whatever they like to this file, but -# should keep this content at the top. -# ---------------------------------------------------------------------------- -from ._astropy_init import * - -# For egg_info test builds to pass, put package imports here. -if not _ASTROPY_SETUP_: - from .core import * - from .parser_helper import Shape +from .core import * +from .core import open +from .parser_helper import Shape +from .version import __version__ diff --git a/pyregion/_astropy_init.py b/pyregion/_astropy_init.py deleted file mode 100644 index 1e229e1..0000000 --- a/pyregion/_astropy_init.py +++ /dev/null @@ -1,60 +0,0 @@ -# Licensed under a 3-clause BSD style license - see LICENSE.rst - -__all__ = ['__version__', '__githash__'] - -# this indicates whether or not we are in the package's setup.py -try: - _ASTROPY_SETUP_ -except NameError: - from sys import version_info - if version_info[0] >= 3: - import builtins - else: - import __builtin__ as builtins - builtins._ASTROPY_SETUP_ = False - -try: - from .version import version as __version__ -except ImportError: - __version__ = '' -try: - from .version import githash as __githash__ -except ImportError: - __githash__ = '' - - -if not _ASTROPY_SETUP_: # noqa - import os - from warnings import warn - from astropy.config.configuration import ( - update_default_config, - ConfigurationDefaultMissingError, - ConfigurationDefaultMissingWarning) - - # Create the test function for self test - from astropy.tests.runner import TestRunner - test = TestRunner.make_test_runner_in(os.path.dirname(__file__)) - test.__test__ = False - __all__ += ['test'] - - # add these here so we only need to cleanup the namespace at the end - config_dir = None - - if not os.environ.get('ASTROPY_SKIP_CONFIG_UPDATE', False): - config_dir = os.path.dirname(__file__) - config_template = os.path.join(config_dir, __package__ + ".cfg") - if os.path.isfile(config_template): - try: - update_default_config( - __package__, config_dir, version=__version__) - except TypeError as orig_error: - try: - update_default_config(__package__, config_dir) - except ConfigurationDefaultMissingError as e: - wmsg = (e.args[0] + - " Cannot install default profile. If you are " - "importing from source, this is expected.") - warn(ConfigurationDefaultMissingWarning(wmsg)) - del e - except Exception: - raise orig_error diff --git a/pyregion/conftest.py b/pyregion/conftest.py index 1b2e2b2..cd0c1ca 100644 --- a/pyregion/conftest.py +++ b/pyregion/conftest.py @@ -1,40 +1,18 @@ -# this contains imports plugins that configure py.test for astropy tests. -# by importing them here in conftest.py they are discoverable by py.test -# no matter how it is invoked within the source tree. - -from astropy.version import version as astropy_version -if astropy_version < '3.0': - # With older versions of Astropy, we actually need to import the pytest - # plugins themselves in order to make them discoverable by pytest. - from astropy.tests.pytest_plugins import * -else: - # As of Astropy 3.0, the pytest plugins provided by Astropy are - # automatically made available when Astropy is installed. This means it's - # not necessary to import them here, but we still need to import global - # variables that are used for configuration. - from astropy.tests.plugins.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS - -from astropy.tests.helper import enable_deprecations_as_exceptions +try: + from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS +except ImportError: # In case this plugin is not installed + PYTEST_HEADER_MODULES = {} + TESTED_VERSIONS = {} -# Uncomment the following line to treat all DeprecationWarnings as exceptions -enable_deprecations_as_exceptions() -PYTEST_HEADER_MODULES.clear() -PYTEST_HEADER_MODULES.update([ - ('numpy', 'numpy'), - ('cython', 'cython'), - ('Astropy', 'astropy'), - ('pyparsing', 'pyparsing'), - ('matplotlib', 'matplotlib'), -]) +def pytest_configure(config): + from pyregion import __version__ as version -# This is to figure out the affiliated package version, rather than -# using Astropy's -try: - from .version import version -except ImportError: - version = 'dev' + config.option.astropy_header = True -import os -packagename = os.path.basename(os.path.dirname(__file__)) -TESTED_VERSIONS[packagename] = version + PYTEST_HEADER_MODULES.pop('Scipy', None) + PYTEST_HEADER_MODULES.pop('h5py', None) + PYTEST_HEADER_MODULES.pop('Pandas', None) + PYTEST_HEADER_MODULES['Astropy'] = 'astropy' + PYTEST_HEADER_MODULES['pyparsing'] = 'pyparsing' + TESTED_VERSIONS['pyregion'] = version diff --git a/pyregion/mpl_helper.py b/pyregion/mpl_helper.py index 164a643..135d85f 100644 --- a/pyregion/mpl_helper.py +++ b/pyregion/mpl_helper.py @@ -56,7 +56,7 @@ def properties_func_default(shape, saved_attrs): if shape.name == "text": kwargs = dict(color=color, - rotation=attr_dict.get("textangle", 0), + rotation=float(attr_dict.get("textangle", 0)), ) font = attr_dict.get("font") if font: diff --git a/pyregion/tests/__init__.py b/pyregion/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyregion/tests/data/test01.reg b/pyregion/tests/data/test01.reg index 809c9d2..2ed27bf 100644 --- a/pyregion/tests/data/test01.reg +++ b/pyregion/tests/data/test01.reg @@ -3,6 +3,6 @@ global color=green dashlist=8 3 width=1 font="helvetica 10 normal" select=1 highlite=1 dash=0 fixed=0 edit=1 move=1 delete=1 include=1 source=1 fk5 ## -ellipse(171.15816,-59.263193,22.632",10.332",317.01716) # width=3 background -ascircle(171.10096,-59.250612,18.510811") # color=cyan +circle(171.10096,-59.250612,18.510811") # color=cyan box(171.16339,-59.281643,42.804",23.616",19.038396) # width=4 polygon(171.1239,-59.26881,171.09051,-59.262088,171.0985,-59.285735,171.1239,-59.27698) diff --git a/pyregion/tests/setup_package.py b/pyregion/tests/setup_package.py deleted file mode 100644 index 1464684..0000000 --- a/pyregion/tests/setup_package.py +++ /dev/null @@ -1,4 +0,0 @@ -def get_package_data(): - return { - _ASTROPY_PACKAGE_NAME_ + '.tests': ['coveragerc', 'data/*.reg', 'data/*.header'] - } diff --git a/pyregion/tests/test_cube.py b/pyregion/tests/test_cube.py index c02b8a0..8d22017 100644 --- a/pyregion/tests/test_cube.py +++ b/pyregion/tests/test_cube.py @@ -1,9 +1,12 @@ import os from os.path import join + from astropy.io.fits import Header -from .. import parse from numpy.testing import assert_allclose +from pyregion import parse + + rootdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') diff --git a/pyregion/tests/test_ds9_attr_parser.py b/pyregion/tests/test_ds9_attr_parser.py index beddfff..df0342b 100644 --- a/pyregion/tests/test_ds9_attr_parser.py +++ b/pyregion/tests/test_ds9_attr_parser.py @@ -1,4 +1,4 @@ -from ..ds9_attr_parser import get_ds9_attr_parser, get_attr, Ds9AttrParser +from pyregion.ds9_attr_parser import get_ds9_attr_parser, get_attr, Ds9AttrParser def test_attr(): diff --git a/pyregion/tests/test_ds9_region_parser.py b/pyregion/tests/test_ds9_region_parser.py index 64aa064..fbf0491 100644 --- a/pyregion/tests/test_ds9_region_parser.py +++ b/pyregion/tests/test_ds9_region_parser.py @@ -1,6 +1,6 @@ -from ..ds9_region_parser import RegionParser, Global -from ..parser_helper import CoordCommand, Shape -from ..region_numbers import SimpleNumber, AngularDistance +from pyregion.ds9_region_parser import RegionParser, Global +from pyregion.parser_helper import CoordCommand, Shape +from pyregion.region_numbers import SimpleNumber, AngularDistance def test_regionLine(): diff --git a/pyregion/tests/test_get_mask.py b/pyregion/tests/test_get_mask.py index 95a6c7d..6ab563f 100644 --- a/pyregion/tests/test_get_mask.py +++ b/pyregion/tests/test_get_mask.py @@ -2,7 +2,8 @@ import numpy as np from os.path import join from astropy.io.fits import Header -from .. import open as pyregion_open + +from pyregion import open as pyregion_open rootdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') diff --git a/pyregion/tests/test_parser_helper.py b/pyregion/tests/test_parser_helper.py index 30c5bae..1f73281 100644 --- a/pyregion/tests/test_parser_helper.py +++ b/pyregion/tests/test_parser_helper.py @@ -1,8 +1,8 @@ -from ..parser_helper import define_shape +from pyregion.parser_helper import define_shape +from pyregion.region_numbers import CoordOdd, CoordEven, Distance def test_define_shape(): - from ..region_numbers import CoordOdd, CoordEven, Distance args = [s.parser for s in [CoordOdd, CoordEven, Distance]] circle_parser = define_shape("circle", args, args_repeat=None) diff --git a/pyregion/tests/test_region.py b/pyregion/tests/test_region.py index 55c0789..912a622 100644 --- a/pyregion/tests/test_region.py +++ b/pyregion/tests/test_region.py @@ -3,9 +3,11 @@ import numpy as np from os.path import join from astropy.io.fits import Header -from .. import open as pyregion_open from numpy.testing import assert_allclose +from pyregion import open as pyregion_open + + rootdir = join(os.path.dirname(os.path.abspath(__file__)), 'data') diff --git a/pyregion/tests/test_region_numbers.py b/pyregion/tests/test_region_numbers.py index d7f407a..7a62c33 100644 --- a/pyregion/tests/test_region_numbers.py +++ b/pyregion/tests/test_region_numbers.py @@ -1,5 +1,5 @@ from math import pi -from ..region_numbers import (usn, simple_integer, sexadecimal60, Sixty, +from pyregion.region_numbers import (usn, simple_integer, sexadecimal60, Sixty, hms_number, dms_number, angular_distance, CoordOdd, HMS) diff --git a/pyregion/tests/test_wcs.py b/pyregion/tests/test_wcs.py index cb1e4ea..a0e1222 100644 --- a/pyregion/tests/test_wcs.py +++ b/pyregion/tests/test_wcs.py @@ -2,10 +2,11 @@ import os.path from numpy.testing import assert_allclose from astropy.io.fits import Header -from ..ds9_region_parser import ds9_shape_defs -from ..region_numbers import CoordOdd, CoordEven -from .. import wcs_converter -from ..wcs_helper import _calculate_rotation_angle +from pyregion.ds9_region_parser import ds9_shape_defs +from pyregion.region_numbers import CoordOdd, CoordEven +from pyregion import wcs_converter +from pyregion.wcs_helper import _calculate_rotation_angle + rootdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') diff --git a/pyregion/wcs_converter.py b/pyregion/wcs_converter.py index b9481b8..8c9bbbc 100644 --- a/pyregion/wcs_converter.py +++ b/pyregion/wcs_converter.py @@ -89,7 +89,7 @@ def convert_to_imagecoord(shape, header): frame=shape.coord_format, unit='degree', obstime='J2000') new_coordlist.extend( - np.asscalar(x) + x.item() for x in old_coordinate.to_pixel(new_wcs, origin=1) ) diff --git a/setup.cfg b/setup.cfg index 879adcb..bd52b95 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,27 +1,68 @@ -[build_docs] -source-dir = docs -build-dir = docs/_build -all_files = 1 - -[upload_docs] -upload-dir = docs/_build/html -show-response = 1 - -[tool:pytest] -minversion = 2.2 -norecursedirs = build docs/_build - -[ah_bootstrap] -auto_use = True - [metadata] name = pyregion -version = 2.1.1 description = python parser for ds9 region files -# long_description = +long_description = file: README.rst +long_description_content_type = text/x-rst author = Jae-Joon Lee author_email = lee.j.joon@gmail.com license = MIT url = https://github.com/astropy/pyregion -edit_on_github = False -github_project = astropy/pyregion +project_urls = + Documentation = https://pyregion.readthedocs.io/ + Source = https://github.com/astropy/pyregion + Tracker = https://github.com/astropy/pyregion/issues +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Science/Research + License :: OSI Approved :: MIT License + Operating System :: MacOS :: MacOS X + Operating System :: POSIX :: Linux + Programming Language :: Cython + Programming Language :: Python + Programming Language :: Python :: 3 + Topic :: Scientific/Engineering :: Astronomy + +[options] +python_requires = >=3.7 +install_requires = + pyparsing>=2.0 + numpy>=1.16 + astropy>=4.0 +packages = + pyregion + pyregion.tests + pyregion.tests.data +include_package_data = True + +[options.package_data] +pyregion.tests.data = + *.header + *.reg + +[options.extras_require] +test = + pytest + pytest-astropy-header +docs = + matplotlib + sphinx + sphinx-astropy + sphinx-rtd-theme + +[build_docs] +source_dir = docs +build_dir = docs/_build +all_files = 1 + +[upload_docs] +upload_dir = docs/_build/html +show_response = 1 + +[tool:pytest] +minversion = 6.0 +norecursedirs = + build + docs/_build +testpaths = + pyregion + docs diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 03a1f5e..b4d1ec0 --- a/setup.py +++ b/setup.py @@ -1,150 +1,20 @@ #!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst -import glob -import os -import sys - -# ah_bootstrap fails with "python -m build". Disabling it for now. - -# import ah_bootstrap -from setuptools import setup - -# A dirty hack to get around some early import/configurations ambiguities -if sys.version_info[0] >= 3: - import builtins -else: - import __builtin__ as builtins -builtins._ASTROPY_SETUP_ = True - -from astropy_helpers.setup_helpers import (register_commands, get_debug_option, - get_package_info) -from astropy_helpers.git_helpers import get_git_devstr -from astropy_helpers.version_helpers import generate_version_py - -# Get some values from the setup.cfg -try: - from ConfigParser import ConfigParser -except ImportError: - from configparser import ConfigParser - -conf = ConfigParser() -conf.read(['setup.cfg']) -metadata = dict(conf.items('metadata')) - -PACKAGENAME = metadata.get('name', 'packagename') -DESCRIPTION = metadata.get('description', 'Astropy affiliated package') -AUTHOR = metadata.get('author', '') -AUTHOR_EMAIL = metadata.get('author_email', '') -LICENSE = metadata.get('license', 'unknown') -URL = metadata.get('url', 'http://astropy.org') - -# It tries to import itself, which does not seem to work with 'python -m build'. -# Instead, put the description here. FIXME: This is a temporary workaround. - -# Get the long description from the package's docstring -# __import__(PACKAGENAME) -# package = sys.modules[PACKAGENAME] -# LONG_DESCRIPTION = package.__doc__ - -LONG_DESCRIPTION = """ -pyregion - a Python parser for ds9 region files - -* Code : https://github.com/astropy/pyregion -* Docs : http://pyregion.readthedocs.io/ - -See also the in-development ``regions`` package -at https://github.com/astropy/regions -a new astronomy package for regions based on Astropy. -""" - -# Store the package name in a built-in variable so it's easy -# to get from other parts of the setup infrastructure -builtins._ASTROPY_PACKAGE_NAME_ = PACKAGENAME - -# VERSION should be PEP386 compatible (http://www.python.org/dev/peps/pep-0386) -# VERSION = '1.9.0' -VERSION = metadata.get("version") - -# Indicates if this version is a release version -RELEASE = 'dev' not in VERSION - -if not RELEASE: - VERSION += get_git_devstr(False) - -# Populate the dict of setup command overrides; this should be done before -# invoking any other functionality from distutils since it can potentially -# modify distutils' behavior. -cmdclassd = register_commands(PACKAGENAME, VERSION, RELEASE) - -# Freeze build information in version.py -generate_version_py(PACKAGENAME, VERSION, RELEASE, - get_debug_option(PACKAGENAME)) - -# Treat everything in scripts except README.rst as a script to be installed -scripts = [fname for fname in glob.glob(os.path.join('scripts', '*')) - if os.path.basename(fname) != 'README.rst'] - -# Get configuration information from all of the various subpackages. -# See the docstring for setup_helpers.update_package_files for more -# details. -package_info = get_package_info() - -# Add the project-global data -package_info['package_data'].setdefault(PACKAGENAME, []) -package_info['package_data'][PACKAGENAME].append('data/*') - -# Include all .c files, recursively, including those generated by -# Cython, since we can not do this in MANIFEST.in with a "dynamic" -# directory name. -c_files = [] -for root, dirs, files in os.walk(PACKAGENAME): - for filename in files: - if filename.endswith('.c'): - c_files.append( - os.path.join( - os.path.relpath(root, PACKAGENAME), filename)) -package_info['package_data'][PACKAGENAME].extend(c_files) - -install_requires = [ - 'pyparsing>=2.0', - 'numpy', - 'Cython', - 'astropy>=1.0', -] - -classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Cython', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Topic :: Scientific/Engineering :: Astronomy', -] - -setup( - name=PACKAGENAME, - version=VERSION, - description=DESCRIPTION, - scripts=scripts, - install_requires=install_requires, - provides=[PACKAGENAME], - author=AUTHOR, - author_email=AUTHOR_EMAIL, - license=LICENSE, - url=URL, - long_description=LONG_DESCRIPTION, - cmdclass=cmdclassd, - zip_safe=False, - use_2to3=False, - classifiers=classifiers, - **package_info -) +from setuptools import setup, Extension +from Cython.Build import cythonize +import numpy + + +include_dirs = [numpy.get_include()] +# define_macros = [("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")] +extensions = [ + Extension(name="pyregion._region_filter", + sources=["pyregion/_region_filter.pyx"], + include_dirs=include_dirs, + # define_macros=define_macros, + ), + ] +ext_modules = cythonize(extensions, language_level=3, include_path=["pyregion"]) + +setup(ext_modules=ext_modules)