You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

APLpy

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

APLpy - pypi Package Compare versions

Comparing version
2.0.3
to
2.1.0
+102
.circleci/config.yml
version: 2
jobs:
image-tests-mpl212:
docker:
- image: astropy/image-tests-py36-mpl212:1.10
steps:
- checkout
- run:
name: Install wheel
command: pip3 install wheel
- run:
name: Install dependencies
command: pip3 install .[test] codecov
- run:
name: Run tests
command: MPLBACKEND=Agg pytest --remote-data=any --mpl
- run:
name: Report coverage
command: codecov
image-tests-mpl222:
docker:
- image: astropy/image-tests-py36-mpl222:1.10
steps:
- checkout
- run:
name: Install wheel
command: pip3 install wheel
- run:
name: Install dependencies
command: pip3 install .[test] codecov
- run:
name: Run tests
command: MPLBACKEND=Agg pytest --remote-data=any --mpl
- run:
name: Report coverage
command: codecov
image-tests-mpl302:
docker:
- image: astropy/image-tests-py37-mpl302:1.10
steps:
- checkout
- run:
name: Install wheel
command: pip3 install wheel
- run:
name: Install dependencies
command: pip3 install .[test] codecov
- run:
name: Run tests
command: MPLBACKEND=Agg pytest --remote-data=any --mpl
- run:
name: Report coverage
command: codecov
image-tests-mpl311:
docker:
- image: astropy/image-tests-py37-mpl311:1.10
steps:
- checkout
- run:
name: Install wheel
command: pip3 install wheel
- run:
name: Install dependencies
command: pip3 install .[test] codecov
- run:
name: Run tests
command: MPLBACKEND=Agg pytest --remote-data=any --mpl
- run:
name: Report coverage
command: codecov
image-tests-mpldev:
docker:
- image: astropy/image-tests-py37-base:1.4
steps:
- checkout
- run:
name: Install wheel
command: pip3 install wheel
- run:
name: Install Matplotlib
command: pip3 install git+https://github.com/matplotlib/matplotlib.git
- run:
name: Install dependencies
command: pip3 install .[test] codecov
- run:
name: Run tests
command: MPLBACKEND=Agg pytest --remote-data=any --mpl
- run:
name: Report coverage
command: codecov
workflows:
version: 2
build_and_test:
jobs:
- image-tests-mpl212
- image-tests-mpl222
- image-tests-mpl302
- image-tests-mpl311
- image-tests-mpldev
# Compiled files
*.py[co]
*.a
*.o
*.so
__pycache__
# Ignore .c files by default to avoid including generated code. If you want to
# add a non-generated .c extension, use `git add -f filename.c`.
*.c
# Other generated files
*/version.py
*/cython_version.py
htmlcov
.coverage
# Sphinx
_build
# Packages/installer info
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
distribute-*.tar.gz
# Other
.*.swp
*~
.project
.pydevproject
.settings
# Mac OSX
.DS_Store
.tox
docs/api
.tmp
version: 2
build:
image: latest
python:
version: 3.7
install:
- method: pip
path: .
extra_requirements:
- docs
- all
formats: []

Sorry, the diff of this file is not supported yet

Metadata-Version: 2.1
Name: aplpy
Version: 2.1.0
Summary: The Astronomical Plotting Library in Python
Home-page: http://aplpy.github.io
Author: Thomas Robitaille and Eli Bressert
Author-email: thomas.robitaille@gmail.com
License: MIT
Platform: UNKNOWN
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
Provides-Extra: test
Provides-Extra: docs
License-File: LICENSE.md
|Azure| |CircleCI| |codecov| |Documentation Status|
About
-----
APLpy (the Astronomical Plotting Library in Python) is a Python module
aimed at producing publication-quality plots of astronomical imaging
data in FITS format.
PyPI: https://pypi.python.org/pypi/APLpy
Website: http://aplpy.github.io
Documentation: http://aplpy.readthedocs.io
APLpy is released under an MIT open-source license.
Installing
----------
You can install APLpy and all its dependencies with::
pip install aplpy
.. |Azure| image:: https://dev.azure.com/thomasrobitaille/aplpy/_apis/build/status/aplpy.aplpy?repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge
:target: https://dev.azure.com/thomasrobitaille/aplpy/_build/latest?definitionId=16&repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge
.. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/main.svg?style=svg
:target: https://circleci.com/gh/aplpy/aplpy/tree/main
.. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/main/graph/badge.svg
:target: https://codecov.io/gh/aplpy/aplpy
.. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat
:target: https://aplpy.readthedocs.io/en/latest/
numpy
astropy>=3.1
matplotlib>=2.0
reproject>=0.4
pyregion>=2.0
pillow>=4.3
pyavm>=0.9.4
scikit-image>=0.14
shapely>=1.7
[docs]
sphinx-astropy
[test]
pytest-astropy
pytest-mpl
.gitignore
.readthedocs.yml
CHANGES.rst
CITATION
LICENSE.md
MANIFEST.in
README.rst
azure-pipelines.yml
conftest.py
pyproject.toml
setup.cfg
setup.py
tox.ini
.circleci/config.yml
aplpy/__init__.py
aplpy/_astropy_init.py
aplpy/axis_labels.py
aplpy/colorbar.py
aplpy/compat.py
aplpy/conftest.py
aplpy/convolve_util.py
aplpy/core.py
aplpy/decorators.py
aplpy/frame.py
aplpy/grid.py
aplpy/header.py
aplpy/layers.py
aplpy/overlays.py
aplpy/regions.py
aplpy/rgb.py
aplpy/slicer.py
aplpy/tick_labels.py
aplpy/ticks.py
aplpy/version.py
aplpy.egg-info/PKG-INFO
aplpy.egg-info/SOURCES.txt
aplpy.egg-info/dependency_links.txt
aplpy.egg-info/not-zip-safe
aplpy.egg-info/requires.txt
aplpy.egg-info/top_level.txt
aplpy/tests/__init__.py
aplpy/tests/coveragerc
aplpy/tests/helpers.py
aplpy/tests/setup_package.py
aplpy/tests/test_axis_labels.py
aplpy/tests/test_beam.py
aplpy/tests/test_colorbar.py
aplpy/tests/test_contour.py
aplpy/tests/test_convolve.py
aplpy/tests/test_downsample.py
aplpy/tests/test_frame.py
aplpy/tests/test_grid.py
aplpy/tests/test_images.py
aplpy/tests/test_init_cube.py
aplpy/tests/test_init_image.py
aplpy/tests/test_layers.py
aplpy/tests/test_misc.py
aplpy/tests/test_pixworldmarkers.py
aplpy/tests/test_rgb.py
aplpy/tests/test_save.py
aplpy/tests/test_scalebar.py
aplpy/tests/test_subplot.py
aplpy/tests/test_tick_labels.py
aplpy/tests/test_ticks.py
aplpy/tests/test_vectors.py
aplpy/tests/data/__init__.py
aplpy/tests/data/shapes.reg
aplpy/tests/data/2d_fits/1904-66_AIR.hdr
aplpy/tests/data/2d_fits/1904-66_AIT.hdr
aplpy/tests/data/2d_fits/1904-66_ARC.hdr
aplpy/tests/data/2d_fits/1904-66_AZP.hdr
aplpy/tests/data/2d_fits/1904-66_BON.hdr
aplpy/tests/data/2d_fits/1904-66_CAR.hdr
aplpy/tests/data/2d_fits/1904-66_CEA.hdr
aplpy/tests/data/2d_fits/1904-66_COD.hdr
aplpy/tests/data/2d_fits/1904-66_COE.hdr
aplpy/tests/data/2d_fits/1904-66_COO.hdr
aplpy/tests/data/2d_fits/1904-66_COP.hdr
aplpy/tests/data/2d_fits/1904-66_CSC.hdr
aplpy/tests/data/2d_fits/1904-66_CYP.hdr
aplpy/tests/data/2d_fits/1904-66_HPX.hdr
aplpy/tests/data/2d_fits/1904-66_MER.hdr
aplpy/tests/data/2d_fits/1904-66_MOL.hdr
aplpy/tests/data/2d_fits/1904-66_NCP.hdr
aplpy/tests/data/2d_fits/1904-66_PAR.hdr
aplpy/tests/data/2d_fits/1904-66_PCO.hdr
aplpy/tests/data/2d_fits/1904-66_QSC.hdr
aplpy/tests/data/2d_fits/1904-66_SFL.hdr
aplpy/tests/data/2d_fits/1904-66_SIN.hdr
aplpy/tests/data/2d_fits/1904-66_STG.hdr
aplpy/tests/data/2d_fits/1904-66_SZP.hdr
aplpy/tests/data/2d_fits/1904-66_TAN.hdr
aplpy/tests/data/2d_fits/1904-66_TSC.hdr
aplpy/tests/data/2d_fits/1904-66_ZEA.hdr
aplpy/tests/data/2d_fits/1904-66_ZPN.hdr
aplpy/tests/data/2d_fits/2MASS_k.hdr
aplpy/tests/data/2d_fits/2MASS_k_rot.hdr
aplpy/tests/data/3d_fits/cube.hdr
docs/Makefile
docs/aplpy_logo.png
docs/conf.py
docs/index.rst
docs/index.txt
docs/make.bat
docs/rgb.rst
docs/_templates/autosummary/base.rst
docs/_templates/autosummary/class.rst
docs/_templates/autosummary/module.rst
docs/fitsfigure/arbitrary_coordinate_systems.rst
docs/fitsfigure/buttons.png
docs/fitsfigure/howto_avm.rst
docs/fitsfigure/howto_noninteractive.rst
docs/fitsfigure/howto_subplot.rst
docs/fitsfigure/myfirstplot.png
docs/fitsfigure/quick_reference.rst
docs/fitsfigure/quickstart.rst
docs/fitsfigure/slicing.rst
resources:
repositories:
- repository: OpenAstronomy
type: github
endpoint: aplpy
name: OpenAstronomy/azure-pipelines-templates
ref: master
jobs:
- template: run-tox-env.yml@OpenAstronomy
parameters:
coverage: codecov
envs:
- linux: codestyle
- macos: py37-test-cov
- macos: py38-test-cov
- macos: py39-test-cov
- macos: py310-test-cov
- linux: py36-test-oldestdeps-cov
- linux: py37-test-cov
- linux: py38-test-cov
- linux: py39-test-cov
- linux: py310-test-devdeps-cov
- windows: py36-test-oldestdeps-cov
- windows: py37-test-cov
- windows: py38-test-cov
- windows: py39-test-cov
- windows: py310-test-cov
- macos: build_docs
- linux: build_docs
- macos: build_docs
include README.rst
include CHANGES.rst
include setup.cfg
include LICENSE.rst
include pyproject.toml
recursive-include aplpy *.pyx *.c *.pxd
recursive-include docs *
recursive-include licenses *
recursive-include scripts *
prune build
prune docs/_build
prune docs/api
global-exclude *.pyc *.o
[build-system]
requires = ["setuptools",
"setuptools_scm",
"wheel"]
build-backend = 'setuptools.build_meta'
[tox]
envlist =
py{36,37,38}-test{,-alldeps,-devdeps}{,-cov}
py{36,37,38}-test-numpy{116,117,118}
py{36,37,38}-test-astropy{30,40,lts}
build_docs
linkcheck
codestyle
requires =
setuptools >= 30.3.0
pip >= 19.3.1
isolated_build = true
[testenv]
# Pass through the following environment variables which may be needed for the CI
passenv = HOME WINDIR LC_ALL LC_CTYPE CC CI TRAVIS
# Run the tests in a temporary directory to make sure that we don't import
# this package from the source tree
changedir = .tmp/{envname}
# tox environments are constructed with so-called 'factors' (or terms)
# separated by hyphens, e.g. test-devdeps-cov. Lines below starting with factor:
# will only take effect if that factor is included in the environment name. To
# see a list of example environments that can be run, along with a description,
# run:
#
# tox -l -v
#
description =
run tests
alldeps: with all optional dependencies
devdeps: with the latest developer version of key dependencies
oldestdeps: with the oldest supported version of key dependencies
cov: and test coverage
numpy116: with numpy 1.16.*
numpy117: with numpy 1.17.*
numpy118: with numpy 1.18.*
astropy30: with astropy 3.0.*
astropy40: with astropy 4.0.*
astropylts: with the latest astropy LTS
# The following provides some specific pinnings for key packages
deps =
numpy116: numpy==1.16.*
numpy117: numpy==1.17.*
numpy118: numpy==1.18.*
astropy30: astropy==3.0.*
astropy40: astropy==4.0.*
astropylts: astropy==4.0.*
devdeps: git+https://github.com/numpy/numpy.git#egg=numpy
devdeps: git+https://github.com/astropy/astropy.git#egg=astropy
oldestdeps: astropy==3.1.*
oldestdeps: matplotlib==2.0.*
oldestdeps: reproject==0.4.*
oldestdeps: pyregion==2.0.*
oldestdeps: pillow==4.3.*
oldestdeps: pyavm==0.9.4
oldestdeps: scikit-image==0.14.*
oldestdeps: shapely==1.7.*
oldestdeps: pytest==4.*
# The following indicates which extras_require from setup.cfg will be installed
extras =
test
alldeps: all
commands =
pip freeze
!cov: pytest --pyargs aplpy {toxinidir}/docs {posargs}
cov: pytest --pyargs aplpy {toxinidir}/docs --cov aplpy --cov-config={toxinidir}/setup.cfg {posargs}
[testenv:build_docs]
changedir = docs
description = invoke sphinx-build to build the HTML docs
extras = docs
commands =
pip freeze
sphinx-build -W -b html . _build/html
[testenv:linkcheck]
changedir = docs
description = check the links in the HTML docs
extras = docs
commands =
pip freeze
sphinx-build -W -b linkcheck . _build/html
[testenv:codestyle]
skip_install = true
changedir = .
description = check code style, e.g. with flake8
deps = flake8
commands = flake8 aplpy --count --ignore=E501,W503,W504
+8
-95
# Licensed under a 3-clause BSD style license - see LICENSE.rst
__all__ = ['__version__', '__githash__', 'test']
__all__ = ['__version__']

@@ -9,7 +9,3 @@ # this indicates whether or not we are in the package's setup.py

except NameError:
from sys import version_info
if version_info[0] >= 3:
import builtins
else:
import __builtin__ as builtins
import builtins
builtins._ASTROPY_SETUP_ = False

@@ -21,93 +17,4 @@

__version__ = ''
try:
from .version import githash as __githash__
except ImportError:
__githash__ = ''
# set up the test command
def _get_test_runner():
import os
from astropy.tests.helper import TestRunner
return TestRunner(os.path.dirname(__file__))
def test(package=None, test_path=None, args=None, plugins=None,
verbose=False, pastebin=None, remote_data=False, pep8=False,
pdb=False, coverage=False, open_files=False, **kwargs):
"""
Run the tests using `py.test <https://docs.pytest.org/en/latest/>`__.
Parameters
----------
package : str, optional
The name of a specific package to test, e.g. 'io.fits' or 'utils'.
If nothing is specified all default tests are run.
test_path : str, optional
Specify location to test by path. May be a single file or
directory. Must be specified absolutely or relative to the
calling directory.
args : str, optional
Additional arguments to be passed to pytest.main in the ``args``
keyword argument.
plugins : list, optional
Plugins to be passed to pytest.main in the ``plugins`` keyword
argument.
verbose : bool, optional
Convenience option to turn on verbose output from py.test. Passing
True is the same as specifying ``'-v'`` in ``args``.
pastebin : {'failed','all',None}, optional
Convenience option for turning on py.test pastebin output. Set to
``'failed'`` to upload info for failed tests, or ``'all'`` to upload
info for all tests.
remote_data : bool, optional
Controls whether to run tests marked with @remote_data. These
tests use online data and are not run by default. Set to True to
run these tests.
pep8 : bool, optional
Turn on PEP8 checking via the `pytest-pep8 plugin
<https://pypi.org/project/pytest-pep8/>`_ and disable normal
tests. Same as specifying ``'--pep8 -k pep8'`` in ``args``.
pdb : bool, optional
Turn on PDB post-mortem analysis for failing tests. Same as
specifying ``'--pdb'`` in ``args``.
coverage : bool, optional
Generate a test coverage report. The result will be placed in
the directory htmlcov.
open_files : bool, optional
Fail when any tests leave files open. Off by default, because
this adds extra run time to the test suite. Requires the
`psutil <https://pypi.org/project/psutil/>`_ package.
parallel : int, optional
When provided, run the tests in parallel on the specified
number of CPUs. If parallel is negative, it will use the all
the cores on the machine. Requires the
`pytest-xdist <https://pypi.org/project/pytest-xdist/>`_ plugin
installed. Only available when using Astropy 0.3 or later.
kwargs
Any additional keywords passed into this function will be passed
on to the astropy test runner. This allows use of test-related
functionality implemented in later versions of astropy without
explicitly updating the package template.
"""
test_runner = _get_test_runner()
return test_runner.run_tests(
package=package, test_path=test_path, args=args,
plugins=plugins, verbose=verbose, pastebin=pastebin,
remote_data=remote_data, pep8=pep8, pdb=pdb,
coverage=coverage, open_files=open_files, **kwargs)
if not _ASTROPY_SETUP_: # noqa

@@ -121,2 +28,8 @@ import os

# 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

@@ -123,0 +36,0 @@ config_dir = None

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
from astropy.wcs.utils import wcs_to_celestial_frame

@@ -4,0 +2,0 @@ from astropy.coordinates import (ICRS, FK5, FK4, Galactic,

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import warnings

@@ -4,0 +2,0 @@

@@ -1,26 +0,42 @@

from __future__ import absolute_import, print_function, division
# This file is used to configure the behavior of pytest when using the Astropy
# test infrastructure. It needs to live inside the package in order for it to
# get picked up when running the tests inside an interpreter using
# packagename.test
# Force the backend to Agg when testing
import os
import matplotlib
matplotlib.use('Agg')
from astropy.version import version as astropy_version
# This file is used to configure the behavior of pytest when using the Astropy
# test infrastructure.
from astropy.version import version as astropy_version
# For Astropy 3.0 and later, we can use the standalone pytest plugin
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 * # noqa
del pytest_report_header
ASTROPY_HEADER = True
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
try:
from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS
ASTROPY_HEADER = True
except ImportError:
ASTROPY_HEADER = False
from astropy.tests.helper import enable_deprecations_as_exceptions
# Uncomment the following line to treat all DeprecationWarnings as
def pytest_configure(config):
if ASTROPY_HEADER:
config.option.astropy_header = True
# Customize the following lines to add/remove entries from the list of
# packages for which version numbers are displayed when running the tests.
PYTEST_HEADER_MODULES['Astropy'] = 'astropy'
PYTEST_HEADER_MODULES['pyregion'] = 'pyregion'
PYTEST_HEADER_MODULES['PyAVM'] = 'PyAVM'
PYTEST_HEADER_MODULES['reproject'] = 'reproject'
del PYTEST_HEADER_MODULES['h5py']
from . import __version__
packagename = os.path.basename(os.path.dirname(__file__))
TESTED_VERSIONS[packagename] = __version__
# Uncomment the last two lines in this block to treat all DeprecationWarnings as
# exceptions. For Astropy v2.0 or later, there are 2 additional keywords,

@@ -35,33 +51,3 @@ # as follow (although default should work for most cases).

# warnings_to_ignore_by_pyver={(MAJOR, MINOR): ['Message to ignore']}
enable_deprecations_as_exceptions()
# Uncomment and customize the following lines to add/remove entries from
# the list of packages for which version numbers are displayed when running
# the tests. Making it pass for KeyError is essential in some cases when
# the package uses other astropy affiliated packages.
try:
PYTEST_HEADER_MODULES['Astropy'] = 'astropy'
PYTEST_HEADER_MODULES['pyregion'] = 'pyregion'
PYTEST_HEADER_MODULES['PyAVM'] = 'PyAVM'
PYTEST_HEADER_MODULES['reproject'] = 'reproject'
del PYTEST_HEADER_MODULES['h5py']
except (NameError, KeyError):
pass
# Uncomment the following lines to display the version number of the
# package rather than the version number of Astropy in the top line when
# running the tests.
import os
# This is to figure out the package version, rather than
# using Astropy's
try:
from .version import version
except ImportError:
version = 'dev'
try:
packagename = os.path.basename(os.path.dirname(__file__))
TESTED_VERSIONS[packagename] = version
except NameError: # Needed to support Astropy <= 1.0.0
pass
# from astropy.tests.helper import enable_deprecations_as_exceptions # noqa
# enable_deprecations_as_exceptions()

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import numpy as np

@@ -4,0 +2,0 @@

@@ -1,6 +0,4 @@

from __future__ import absolute_import, print_function, division
import threading
from astropy.utils.decorators import wraps
from functools import wraps

@@ -7,0 +5,0 @@ mydata = threading.local()

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
from .decorators import auto_refresh

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import numpy as np

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
from astropy import log

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
from collections import OrderedDict

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import warnings

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
from astropy import log

@@ -4,0 +2,0 @@ from astropy import wcs

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
from distutils import version

@@ -4,0 +2,0 @@ import os

@@ -1,4 +0,2 @@

from __future__ import absolute_import, print_function, division
def slice_hypercube(data, header, dimensions=[0, 1], slices=[]):

@@ -5,0 +3,0 @@ """

@@ -7,2 +7,7 @@ import matplotlib

# The developer versions of the form 3.2.x+... contain changes that will only
# be included in the 3.3.x release, so we update this here.
if MPL_VERSION[:3] == '3.2' and '+' in MPL_VERSION:
MPL_VERSION = '3.3'
baseline_dir = ROOT + '/' + MPL_VERSION[:3] + '.x/'

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import string

@@ -4,0 +2,0 @@ import random

@@ -1,5 +0,3 @@

from __future__ import absolute_import, print_function, division
def get_package_data():
return {_ASTROPY_PACKAGE_NAME_ + '.tests': ['coveragerc', 'data/*.reg', 'data/*/*.hdr']} # noqa

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import numpy as np

@@ -4,0 +2,0 @@ from astropy.tests.helper import pytest

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import numpy as np

@@ -4,0 +2,0 @@ from astropy.tests.helper import pytest

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import pytest

@@ -4,0 +2,0 @@ import numpy as np

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import numpy as np

@@ -4,0 +2,0 @@ from astropy.io import fits

@@ -1,4 +0,1 @@

from __future__ import absolute_import, print_function, division
import pytest
import numpy as np

@@ -5,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import numpy as np

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import pytest

@@ -4,0 +2,0 @@ import numpy as np

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -4,0 +2,0 @@ import tempfile

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import pytest

@@ -4,0 +2,0 @@ import numpy as np

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import numpy as np

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -4,0 +2,0 @@ import warnings

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -32,3 +30,3 @@ import sys

from xml.dom import minidom
return minidom.parse(f).childNodes[2].attributes['xmlns'].value == 'http://www.w3.org/2000/svg'
return minidom.parse(f).childNodes[-1].attributes['xmlns'].value == 'http://www.w3.org/2000/svg'
else:

@@ -35,0 +33,0 @@ raise Exception("Unknown format: %s" % format)

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import pytest

@@ -4,0 +2,0 @@ import numpy as np

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import os

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import numpy as np

@@ -4,0 +2,0 @@

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
import pytest

@@ -4,0 +2,0 @@ import numpy as np

@@ -1,3 +0,1 @@

from __future__ import absolute_import, print_function, division
from .decorators import auto_refresh

@@ -22,4 +20,4 @@

if direction in ('in', 'out'):
self.ax.coords[self.x].ticks.set_tick_out(direction == 'out')
self.ax.coords[self.y].ticks.set_tick_out(direction == 'out')
self._ax.coords[self.x].ticks.set_tick_out(direction == 'out')
self._ax.coords[self.y].ticks.set_tick_out(direction == 'out')
else:

@@ -26,0 +24,0 @@ raise ValueError("direction should be 'in' or 'out'")

@@ -1,227 +0,8 @@

# Autogenerated by Astropy-affiliated package aplpy's setup.py on 2019-02-19 11:43:15 UTC
from __future__ import unicode_literals
import datetime
import locale
import os
import subprocess
import warnings
def _decode_stdio(stream):
try:
stdio_encoding = locale.getdefaultlocale()[1] or 'utf-8'
except ValueError:
stdio_encoding = 'utf-8'
try:
text = stream.decode(stdio_encoding)
except UnicodeDecodeError:
# Final fallback
text = stream.decode('latin1')
return text
def update_git_devstr(version, path=None):
"""
Updates the git revision string if and only if the path is being imported
directly from a git working copy. This ensures that the revision number in
the version string is accurate.
"""
try:
# Quick way to determine if we're in git or not - returns '' if not
devstr = get_git_devstr(sha=True, show_warning=False, path=path)
except OSError:
return version
if not devstr:
# Probably not in git so just pass silently
return version
if 'dev' in version: # update to the current git revision
version_base = version.split('.dev', 1)[0]
devstr = get_git_devstr(sha=False, show_warning=False, path=path)
return version_base + '.dev' + devstr
else:
# otherwise it's already the true/release version
return version
def get_git_devstr(sha=False, show_warning=True, path=None):
"""
Determines the number of revisions in this repository.
Parameters
----------
sha : bool
If True, the full SHA1 hash will be returned. Otherwise, the total
count of commits in the repository will be used as a "revision
number".
show_warning : bool
If True, issue a warning if git returns an error code, otherwise errors
pass silently.
path : str or None
If a string, specifies the directory to look in to find the git
repository. If `None`, the current working directory is used, and must
be the root of the git repository.
If given a filename it uses the directory containing that file.
Returns
-------
devversion : str
Either a string with the revision number (if `sha` is False), the
SHA1 hash of the current commit (if `sha` is True), or an empty string
if git version info could not be identified.
"""
if path is None:
path = os.getcwd()
if not os.path.isdir(path):
path = os.path.abspath(os.path.dirname(path))
if sha:
# Faster for getting just the hash of HEAD
cmd = ['rev-parse', 'HEAD']
else:
cmd = ['rev-list', '--count', 'HEAD']
def run_git(cmd):
try:
p = subprocess.Popen(['git'] + cmd, cwd=path,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
stdout, stderr = p.communicate()
except OSError as e:
if show_warning:
warnings.warn('Error running git: ' + str(e))
return (None, b'', b'')
if p.returncode == 128:
if show_warning:
warnings.warn('No git repository present at {0!r}! Using '
'default dev version.'.format(path))
return (p.returncode, b'', b'')
if p.returncode == 129:
if show_warning:
warnings.warn('Your git looks old (does it support {0}?); '
'consider upgrading to v1.7.2 or '
'later.'.format(cmd[0]))
return (p.returncode, stdout, stderr)
elif p.returncode != 0:
if show_warning:
warnings.warn('Git failed while determining revision '
'count: {0}'.format(_decode_stdio(stderr)))
return (p.returncode, stdout, stderr)
return p.returncode, stdout, stderr
returncode, stdout, stderr = run_git(cmd)
if not sha and returncode == 128:
# git returns 128 if the command is not run from within a git
# repository tree. In this case, a warning is produced above but we
# return the default dev version of '0'.
return '0'
elif not sha and returncode == 129:
# git returns 129 if a command option failed to parse; in
# particular this could happen in git versions older than 1.7.2
# where the --count option is not supported
# Also use --abbrev-commit and --abbrev=0 to display the minimum
# number of characters needed per-commit (rather than the full hash)
cmd = ['rev-list', '--abbrev-commit', '--abbrev=0', 'HEAD']
returncode, stdout, stderr = run_git(cmd)
# Fall back on the old method of getting all revisions and counting
# the lines
if returncode == 0:
return str(stdout.count(b'\n'))
else:
return ''
elif sha:
return _decode_stdio(stdout)[:40]
else:
return _decode_stdio(stdout).strip()
# This function is tested but it is only ever executed within a subprocess when
# creating a fake package, so it doesn't get picked up by coverage metrics.
def _get_repo_path(pathname, levels=None): # pragma: no cover
"""
Given a file or directory name, determine the root of the git repository
this path is under. If given, this won't look any higher than ``levels``
(that is, if ``levels=0`` then the given path must be the root of the git
repository and is returned if so.
Returns `None` if the given path could not be determined to belong to a git
repo.
"""
if os.path.isfile(pathname):
current_dir = os.path.abspath(os.path.dirname(pathname))
elif os.path.isdir(pathname):
current_dir = os.path.abspath(pathname)
else:
return None
current_level = 0
while levels is None or current_level <= levels:
if os.path.exists(os.path.join(current_dir, '.git')):
return current_dir
current_level += 1
if current_dir == os.path.dirname(current_dir):
break
current_dir = os.path.dirname(current_dir)
return None
_packagename = "aplpy"
_last_generated_version = "2.0.3"
_last_githash = "70f1a5fedfc04f8d97ef71d2078c47cf5efec9b3"
# Determine where the source code for this module
# lives. If __file__ is not a filesystem path then
# it is assumed not to live in a git repo at all.
if _get_repo_path(__file__, levels=len(_packagename.split('.'))):
version = update_git_devstr(_last_generated_version, path=__file__)
githash = get_git_devstr(sha=True, show_warning=False,
path=__file__) or _last_githash
else:
# The file does not appear to live in a git repo so don't bother
# invoking git
version = _last_generated_version
githash = _last_githash
major = 2
minor = 0
bugfix = 3
version_info = (major, minor, bugfix)
release = True
timestamp = datetime.datetime(2019, 2, 19, 11, 43, 15)
debug = False
astropy_helpers_version = "3.1"
# Note that we need to fall back to the hard-coded version if either
# setuptools_scm can't be imported or setuptools_scm can't determine the
# version, so we catch the generic 'Exception'.
try:
from ._compiler import compiler
except ImportError:
compiler = "unknown"
try:
from .cython_version import cython_version
except ImportError:
cython_version = "unknown"
from setuptools_scm import get_version
version = get_version(root='..', relative_to=__file__)
except Exception:
version = '2.1.0'
CHANGES
--------
2.1.0 (2022-03-10)
------------------
- Updated package infrastructure to follow guidelines described in APE 17, namely
removing astropy-helpers and using tox for setting up testing environments. [#448]
- Fix compatibility with astropy 4.x and 5.0. [#448,#471]
- Fixed default color for ``show_lines`` (was previously transparent, now black). [#441]
- Fixed ``set_tick_direction`` to not error due to incorrect reference to ``ax``. [#450]
- Fixed compatibility with Matplotlib 3.4. [#466]
2.0.3 (2019-02-19)

@@ -5,0 +19,0 @@ ------------------

@@ -28,5 +28,6 @@ # -*- coding: utf-8 -*-

import datetime
import os
import sys
import datetime
from importlib import import_module

@@ -40,7 +41,5 @@ try:

# Get configuration information from setup.cfg
try:
from ConfigParser import ConfigParser
except ImportError:
from configparser import ConfigParser
from configparser import ConfigParser
conf = ConfigParser()
conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')])

@@ -51,5 +50,12 @@ setup_cfg = dict(conf.items('metadata'))

# By default, highlight as Python 3.
highlight_language = 'python3'
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.1'
#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

@@ -67,3 +73,3 @@ # directories to ignore when looking for source files.

# This does not *have* to match the package name, but typically does
project = setup_cfg['package_name']
project = setup_cfg['name']
author = setup_cfg['author']

@@ -77,4 +83,4 @@ copyright = '{0}, {1}'.format(

__import__(setup_cfg['package_name'])
package = sys.modules[setup_cfg['package_name']]
import_module(setup_cfg['name'])
package = sys.modules[setup_cfg['name']]

@@ -87,3 +93,3 @@ # The short X.Y version.

# -- Options for HTML output ---------------------------------------------------
# -- Options for HTML output --------------------------------------------------

@@ -97,2 +103,3 @@ # A NOTE ON HTML THEMES

# Add any paths that contain custom themes here, relative to this directory.

@@ -110,2 +117,6 @@ # To use a different custom theme, add the directory containing the theme.

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = ''
# The name of an image file (within the static path) to use as favicon of the

@@ -138,3 +149,3 @@ # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32

# -- Options for manual page output --------------------------------------------
# -- Options for manual page output -------------------------------------------

@@ -147,13 +158,10 @@ # One entry per manual page. List of tuples

## -- Options for the edit_on_github extension ----------------------------------------
# -- Options for the edit_on_github extension ---------------------------------
if eval(setup_cfg.get('edit_on_github')):
extensions += ['astropy.sphinx.ext.edit_on_github']
if setup_cfg.get('edit_on_github').lower() == 'true':
versionmod = __import__(setup_cfg['package_name'] + '.version')
extensions += ['sphinx_astropy.ext.edit_on_github']
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"
edit_on_github_branch = "master"

@@ -163,2 +171,5 @@ edit_on_github_source_root = ""

# -- Resolving issue number to links in changelog -----------------------------
github_issues_url = 'https://github.com/{0}/issues/'.format(setup_cfg['github_project'])
# Use nitpicky mode to avoid broken links in docs

@@ -168,1 +179,27 @@ nitpicky = True

('py:class', 'aplpy.regions.Regions')]
# -- Turn on nitpicky mode for sphinx (to warn about references not found) ----
#
# nitpicky = True
# nitpick_ignore = []
#
# Some warnings are impossible to suppress, and you can list specific references
# that should be ignored in a nitpick-exceptions file which should be inside
# the docs/ directory. The format of the file should be:
#
# <type> <class>
#
# for example:
#
# py:class astropy.io.votable.tree.Element
# py:class astropy.io.votable.tree.SimpleElement
# py:class astropy.io.votable.tree.SimpleElementWithContent
#
# Uncomment the following lines to enable the exceptions:
#
# for line in open('nitpick-exceptions'):
# if line.strip() == "" or line.startswith("#"):
# continue
# dtype, target = line.split(None, 1)
# target = target.strip()
# nitpick_ignore.append((dtype, six.u(target)))
+41
-53
Metadata-Version: 2.1
Name: APLpy
Version: 2.0.3
Name: aplpy
Version: 2.1.0
Summary: The Astronomical Plotting Library in Python
Home-page: http://aplpy.github.io
Author: Thomas Robitaille and Eli Bressert
Author-email: thomas.robitaille@gmail.com, elibre@users.sourceforge.net
Author-email: thomas.robitaille@gmail.com
License: MIT
Description: |Build Status| |CircleCI| |codecov| |Documentation Status|
About
-----
APLpy (the Astronomical Plotting Library in Python) is a Python module
aimed at producing publication-quality plots of astronomical imaging
data in FITS format.
PyPI: https://pypi.python.org/pypi/APLpy
Website: http://aplpy.github.io
Documentation: http://aplpy.readthedocs.io
APLpy is released under an MIT open-source license.
Installing
----------
The following dependencies are required:
- `Numpy <http://www.numpy.org>`__ 1.11 or later
- `Matplotlib <http://www.matplotlib.org>`__ 2.0 or later
- `Astropy <http://www.astropy.org>`__ 3.1 or later
- `reproject <https://reproject.readthedocs.org>`__ 0.4 or later
- `PyAVM <http://astrofrog.github.io/pyavm/>`__ 0.9.4 or later
- `pyregion <http://pyregion.readthedocs.org/>`__ 2.0 or later
- `pillow <https://pypi.org/project/Pillow/>`__ 4.0 or later
- `scikit-image <https://pypi.org/project/scikit-image/>`__ 0.14 or later
- `shapely <https://shapely.readthedocs.io/en/stable/project.html>`__ 1.6 or later
You can install APLpy and all its dependencies with::
pip install aplpy
or if you use conda::
conda install -c astropy aplpy
.. |Build Status| image:: https://travis-ci.org/aplpy/aplpy.svg?branch=master
:target: https://travis-ci.org/aplpy/aplpy
.. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/master.svg?style=svg
:target: https://circleci.com/gh/aplpy/aplpy/tree/master
.. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/master/graph/badge.svg
:target: https://codecov.io/gh/aplpy/aplpy
.. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat
:target: https://aplpy.readthedocs.io/en/latest/
Platform: UNKNOWN
Requires-Python: >=3.5
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
Provides-Extra: test
Provides-Extra: docs
License-File: LICENSE.md
|Azure| |CircleCI| |codecov| |Documentation Status|
About
-----
APLpy (the Astronomical Plotting Library in Python) is a Python module
aimed at producing publication-quality plots of astronomical imaging
data in FITS format.
PyPI: https://pypi.python.org/pypi/APLpy
Website: http://aplpy.github.io
Documentation: http://aplpy.readthedocs.io
APLpy is released under an MIT open-source license.
Installing
----------
You can install APLpy and all its dependencies with::
pip install aplpy
.. |Azure| image:: https://dev.azure.com/thomasrobitaille/aplpy/_apis/build/status/aplpy.aplpy?repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge
:target: https://dev.azure.com/thomasrobitaille/aplpy/_build/latest?definitionId=16&repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge
.. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/main.svg?style=svg
:target: https://circleci.com/gh/aplpy/aplpy/tree/main
.. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/main/graph/badge.svg
:target: https://codecov.io/gh/aplpy/aplpy
.. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat
:target: https://aplpy.readthedocs.io/en/latest/

@@ -1,2 +0,2 @@

|Build Status| |CircleCI| |codecov| |Documentation Status|
|Azure| |CircleCI| |codecov| |Documentation Status|

@@ -21,14 +21,2 @@ About

The following dependencies are required:
- `Numpy <http://www.numpy.org>`__ 1.11 or later
- `Matplotlib <http://www.matplotlib.org>`__ 2.0 or later
- `Astropy <http://www.astropy.org>`__ 3.1 or later
- `reproject <https://reproject.readthedocs.org>`__ 0.4 or later
- `PyAVM <http://astrofrog.github.io/pyavm/>`__ 0.9.4 or later
- `pyregion <http://pyregion.readthedocs.org/>`__ 2.0 or later
- `pillow <https://pypi.org/project/Pillow/>`__ 4.0 or later
- `scikit-image <https://pypi.org/project/scikit-image/>`__ 0.14 or later
- `shapely <https://shapely.readthedocs.io/en/stable/project.html>`__ 1.6 or later
You can install APLpy and all its dependencies with::

@@ -38,13 +26,9 @@

or if you use conda::
conda install -c astropy aplpy
.. |Build Status| image:: https://travis-ci.org/aplpy/aplpy.svg?branch=master
:target: https://travis-ci.org/aplpy/aplpy
.. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/master.svg?style=svg
:target: https://circleci.com/gh/aplpy/aplpy/tree/master
.. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/master/graph/badge.svg
.. |Azure| image:: https://dev.azure.com/thomasrobitaille/aplpy/_apis/build/status/aplpy.aplpy?repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge
:target: https://dev.azure.com/thomasrobitaille/aplpy/_build/latest?definitionId=16&repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge
.. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/main.svg?style=svg
:target: https://circleci.com/gh/aplpy/aplpy/tree/main
.. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/main/graph/badge.svg
:target: https://codecov.io/gh/aplpy/aplpy
.. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat
:target: https://aplpy.readthedocs.io/en/latest/
+70
-30

@@ -1,37 +0,77 @@

[build_sphinx]
source-dir = docs
build-dir = docs/_build
all_files = 1
[metadata]
name = aplpy
author = Thomas Robitaille and Eli Bressert
author_email = thomas.robitaille@gmail.com
license = MIT
license_file = LICENSE.md
url = http://aplpy.github.io
description = The Astronomical Plotting Library in Python
long_description = file: README.rst
long_description_content_type = text/x-rst
edit_on_github = False
github_project = APLpy/aplpy
[build_docs]
source-dir = docs
build-dir = docs/_build
all_files = 1
[options]
zip_safe = False
packages = find:
python_requires = >=3.6
setup_requires = setuptools_scm
install_requires =
numpy
astropy>=3.1
matplotlib>=2.0
reproject>=0.4
pyregion>=2.0
pillow>=4.3
pyavm>=0.9.4
scikit-image>=0.14
shapely>=1.7
[upload_docs]
upload-dir = docs/_build/html
show-response = 1
[options.extras_require]
test =
pytest-astropy
pytest-mpl
docs =
sphinx-astropy
[options.package_data]
aplpy = *.reg, *.hdr
[tool:pytest]
minversion = 3.0
norecursedirs = build docs/_build
testpaths = "aplpy" "docs"
astropy_header = true
doctest_plus = enabled
text_file_format = rst
addopts = --doctest-rst
[ah_bootstrap]
auto_use = True
[coverage:run]
omit =
aplpy/_astropy_init*
aplpy/conftest.py
aplpy/*setup_package*
aplpy/tests/*
aplpy/*/tests/*
aplpy/extern/*
aplpy/version*
*/aplpy/_astropy_init*
*/aplpy/conftest.py
*/aplpy/*setup_package*
*/aplpy/tests/*
*/aplpy/*/tests/*
*/aplpy/extern/*
*/aplpy/version*
[metadata]
package_name = aplpy
description = The Astronomical Plotting Library in Python
author = Thomas Robitaille and Eli Bressert
author_email = thomas.robitaille@gmail.com, elibre@users.sourceforge.net
license = MIT
url = http://aplpy.github.io
edit_on_github = False
github_project = aplpy/aplpy
install_requires = numpy, astropy>=3.1, matplotlib>=2.0, reproject>=0.4, pyregion>=2.0, pillow>=4.0, pyavm>=0.9.4, scikit-image>=0.14, shapely>=1.6
# version should be PEP440 compatible (https://www.python.org/dev/peps/pep-0440/)
version = 2.0.3
[coverage:report]
exclude_lines =
pragma: no cover
except ImportError
raise AssertionError
raise NotImplementedError
def main\(.*\):
pragma: py{ignore_python_version}
def _ipython_key_completions_
[options.extras_require]
docs = sphinx-astropy
test = pytest-astropy; codecov; pytest-mpl
[egg_info]
tag_build =
tag_date = 0
+49
-112
#!/usr/bin/env python
# Licensed under a 3-clause BSD style license - see LICENSE.rst
import glob
# NOTE: The configuration for the package, including the name, version, and
# other information are set in the setup.cfg file.
import os
import sys
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
# First provide helpful messages if contributors try and run legacy commands
# for tests or docs.
# Get some values from the setup.cfg
try:
from ConfigParser import ConfigParser
except ImportError:
from configparser import ConfigParser
TEST_HELP = """
Note: running tests is no longer done using 'python setup.py test'. Instead
you will need to run:
conf = ConfigParser()
conf.read(['setup.cfg'])
metadata = dict(conf.items('metadata'))
tox -e test
PACKAGENAME = metadata.get('package_name', 'packagename')
DESCRIPTION = metadata.get('description', 'packagename')
AUTHOR = metadata.get('author', 'Astropy Developers')
AUTHOR_EMAIL = metadata.get('author_email', '')
LICENSE = metadata.get('license', 'unknown')
URL = metadata.get('url', 'http://astropy.org')
If you don't already have tox installed, you can install it with:
# order of priority for long_description:
# (1) set in setup.cfg,
# (2) load LONG_DESCRIPTION.rst,
# (3) load README.rst,
# (4) package docstring
readme_glob = 'README*'
_cfg_long_description = metadata.get('long_description', '')
if _cfg_long_description:
LONG_DESCRIPTION = _cfg_long_description
pip install tox
elif os.path.exists('LONG_DESCRIPTION.rst'):
with open('LONG_DESCRIPTION.rst') as f:
LONG_DESCRIPTION = f.read()
If you only want to run part of the test suite, you can also use pytest
directly with::
elif len(glob.glob(readme_glob)) > 0:
with open(glob.glob(readme_glob)[0]) as f:
LONG_DESCRIPTION = f.read()
pip install -e .[test]
pytest
else:
# Get the long description from the package's docstring
__import__(PACKAGENAME)
package = sys.modules[PACKAGENAME]
LONG_DESCRIPTION = package.__doc__
For more information, see:
# 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
http://docs.astropy.org/en/latest/development/testguide.html#running-tests
"""
# VERSION should be PEP440 compatible (http://www.python.org/dev/peps/pep-0440)
VERSION = metadata.get('version', '0.0.dev0')
if 'test' in sys.argv:
print(TEST_HELP)
sys.exit(1)
# Indicates if this version is a release version
RELEASE = 'dev' not in VERSION
DOCS_HELP = """
Note: building the documentation is no longer done using
'python setup.py build_docs'. Instead you will need to run:
if not RELEASE:
VERSION += get_git_devstr(False)
tox -e build_docs
# 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)
If you don't already have tox installed, you can install it with:
# Freeze build information in version.py
generate_version_py(PACKAGENAME, VERSION, RELEASE,
get_debug_option(PACKAGENAME))
pip install tox
# Treat everything in scripts except README* as a script to be installed
scripts = [fname for fname in glob.glob(os.path.join('scripts', '*'))
if not os.path.basename(fname).startswith('README')]
You can also build the documentation with Sphinx directly using::
pip install -e .[docs]
cd docs
make html
# 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()
For more information, see:
# Add the project-global data
package_info['package_data'].setdefault(PACKAGENAME, [])
package_info['package_data'][PACKAGENAME].append('data/*')
http://docs.astropy.org/en/latest/install.html#builddocs
"""
# Define entry points for command-line scripts
entry_points = {'console_scripts': []}
if 'build_docs' in sys.argv or 'build_sphinx' in sys.argv:
print(DOCS_HELP)
sys.exit(1)
if conf.has_section('entry_points'):
entry_point_list = conf.items('entry_points')
for entry_point in entry_point_list:
entry_points['console_scripts'].append('{0} = {1}'.format(
entry_point[0], entry_point[1]))
VERSION_TEMPLATE = """
# Note that we need to fall back to the hard-coded version if either
# setuptools_scm can't be imported or setuptools_scm can't determine the
# version, so we catch the generic 'Exception'.
try:
from setuptools_scm import get_version
version = get_version(root='..', relative_to=__file__)
except Exception:
version = '{version}'
""".lstrip()
# 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)
# Note that requires and provides should not be included in the call to
# ``setup``, since these are now deprecated. See this link for more details:
# https://groups.google.com/forum/#!topic/astropy-dev/urYO8ckB2uM
setup(name='APLpy',
version=VERSION,
description=DESCRIPTION,
scripts=scripts,
install_requires=[s.strip() for s in metadata.get('install_requires', 'astropy').split(',')],
author=AUTHOR,
author_email=AUTHOR_EMAIL,
license=LICENSE,
url=URL,
long_description=LONG_DESCRIPTION,
cmdclass=cmdclassd,
zip_safe=False,
use_2to3=False,
entry_points=entry_points,
python_requires='>=3.5',
**package_info
)
setup(use_scm_version={'write_to': os.path.join('aplpy', 'version.py'),
'write_to_template': VERSION_TEMPLATE})
"""
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
__minimum_python_version__ = (3, 5)
if sys.version_info < __minimum_python_version__:
print("ERROR: Python {} or later is required by astropy-helpers".format(
__minimum_python_version__))
sys.exit(1)
try:
from ConfigParser import ConfigParser, RawConfigParser
except ImportError:
from configparser import ConfigParser, RawConfigParser
_str_types = (str, bytes)
# 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...
# In case it didn't successfully import before the ez_setup checks
import pkg_resources
from setuptools import Distribution
from setuptools.package_index import PackageIndex
from distutils import log
from distutils.debug import DEBUG
# TODO: Maybe enable checking for a specific version of astropy_helpers?
DIST_NAME = 'astropy-helpers'
PACKAGE_NAME = 'astropy_helpers'
UPPER_VERSION_EXCLUSIVE = None
# 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)
]
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 not isinstance(path, str):
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 os.path.exists('setup.cfg'):
return {}
cfg = ConfigParser()
try:
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))
return {}
if not cfg.has_section('ah_bootstrap'):
return {}
config = {}
for option, type_ in CFG_OPTIONS:
if not cfg.has_option('ah_bootstrap', option):
continue
if type_ is bool:
value = cfg.getboolean('ah_bootstrap', option)
else:
value = 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(
'^(?P<status>[+-U ])(?P<commit>[0-9a-f]{40}) '
'(?P<submodule>\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, str):
stdout = stdout.decode(stdio_encoding, 'replace')
if not isinstance(stderr, str):
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()

Sorry, the diff of this file is not supported yet

# We set the language to c because python isn't supported on the MacOS X nodes
# on Travis. However, the language ends up being irrelevant anyway, since we
# install Python ourselves using conda.
language: c
os:
- osx
- linux
# Setting sudo to false opts in to Travis-CI container-based builds.
sudo: false
env:
matrix:
- PYTHON_VERSION=3.5
- PYTHON_VERSION=3.6
- PYTHON_VERSION=3.7 SETUPTOOLS_VERSION=dev DEBUG=True
CONDA_DEPENDENCIES='sphinx cython numpy pytest-cov'
EVENT_TYPE='push pull_request cron'
global:
- CONDA_DEPENDENCIES="setuptools sphinx cython numpy pytest-cov"
- PIP_DEPENDENCIES="codecov"
- EVENT_TYPE='push pull_request'
matrix:
include:
# Do one build with sphinx-astropy as one of the tests bypasses the auto-
# installation but we want to make sure that test runs for coverage.
- os: linux
env: PYTHON_VERSION=3.6 PIP_DEPENDENCIES='codecov sphinx-astropy'
- os: linux
env: PYTHON_VERSION=3.6 SPHINX_VERSION='1.5' SETUPTOOLS_VERSION=27
- os: linux
env: PYTHON_VERSION=3.5 SPHINX_VERSION='1.4' SETUPTOOLS_VERSION=27
- os: linux
env: PYTHON_VERSION=3.6 PIP_DEPENDENCIES='git+https://github.com/sphinx-doc/sphinx.git#egg=sphinx codecov'
CONDA_DEPENDENCIES="setuptools cython numpy pytest-cov"
# Test without installing numpy beforehand to make sure everything works
# without assuming numpy is already installed
- os: linux
env: PYTHON_VERSION=3.6 CONDA_DEPENDENCIES='sphinx cython pytest-cov'
# Test conda's clang
- os: osx
env:
- PYTHON_VERSION=3.5
- CONDA_DEPENDENCIES="setuptools sphinx cython numpy pytest-cov clang llvm-openmp"
- OPENMP_EXPECTED=True
- CCOMPILER=clang
# Test gcc on OSX
- os: osx
env:
- PYTHON_VERSION=3.5
- CONDA_DEPENDENCIES="setuptools sphinx cython numpy pytest-cov gcc"
- OPENMP_EXPECTED=True
- CCOMPILER=gcc
# Uncomment the following if there are issues in setuptools that we
# can't work around quickly - otherwise leave uncommented so that
# we notice when things go wrong.
#
# allow_failures:
# - env: PYTHON_VERSION=3.6 SETUPTOOLS_VERSION=dev DEBUG=True
# CONDA_DEPENDENCIES='sphinx cython numpy pytest-cov'
# EVENT_TYPE='push pull_request cron'
before_install:
# Test OSX without OpenMP support
# Since the matrix OSX tests use the OS shipped version of clang, they double up
# as exploratory tests for when the shipped version has automatic OpenMP support.
# These tests will then fail and at such a time a new one should be added
# to explicitly remove OpenMP support.
- if [ -z $OPENMP_EXPECTED ]; then
if [[ $TRAVIS_OS_NAME == osx ]]; then
export OPENMP_EXPECTED=False;
else
export OPENMP_EXPECTED=True;
fi
fi
# We need to use CCOMPILER otherwise Travis overwrites CC if we define it
# in env: above.
- if [ ! -z $CCOMPILER ]; then
export CC=$CCOMPILER;
fi
# Check CC variable
- echo $CC
install:
- git clone git://github.com/astropy/ci-helpers.git
- source ci-helpers/travis/setup_conda.sh
# We cannot install the developer version of setuptools using pip because
# pip tries to remove the previous version of setuptools before the
# installation is complete, which causes issues. Instead, we just install
# setuptools manually.
- if [[ $SETUPTOOLS_VERSION == dev ]]; then git clone http://github.com/pypa/setuptools.git; cd setuptools; python bootstrap.py; python setup.py install; cd ..; fi
before_script:
# Some of the tests use git commands that require a user to be configured
- git config --global user.name "A U Thor"
- git config --global user.email "author@example.com"
script:
- py.test --cov astropy_helpers astropy_helpers
# In conftest.py we produce a .coverage.subprocess that contains coverage
# statistics for sub-processes, so we combine it with the main one here.
- mv .coverage .coverage.main
- coverage combine .coverage.main .coverage.subprocess
- coverage report
after_success:
- codecov
"""
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
__minimum_python_version__ = (3, 5)
if sys.version_info < __minimum_python_version__:
print("ERROR: Python {} or later is required by astropy-helpers".format(
__minimum_python_version__))
sys.exit(1)
try:
from ConfigParser import ConfigParser, RawConfigParser
except ImportError:
from configparser import ConfigParser, RawConfigParser
_str_types = (str, bytes)
# 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...
# In case it didn't successfully import before the ez_setup checks
import pkg_resources
from setuptools import Distribution
from setuptools.package_index import PackageIndex
from distutils import log
from distutils.debug import DEBUG
# TODO: Maybe enable checking for a specific version of astropy_helpers?
DIST_NAME = 'astropy-helpers'
PACKAGE_NAME = 'astropy_helpers'
UPPER_VERSION_EXCLUSIVE = None
# 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)
]
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 not isinstance(path, str):
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 os.path.exists('setup.cfg'):
return {}
cfg = ConfigParser()
try:
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))
return {}
if not cfg.has_section('ah_bootstrap'):
return {}
config = {}
for option, type_ in CFG_OPTIONS:
if not cfg.has_option('ah_bootstrap', option):
continue
if type_ is bool:
value = cfg.getboolean('ah_bootstrap', option)
else:
value = 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(
'^(?P<status>[+-U ])(?P<commit>[0-9a-f]{40}) '
'(?P<submodule>\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, str):
stdout = stdout.decode(stdio_encoding, 'replace')
if not isinstance(stderr, str):
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()
# AppVeyor.com is a Continuous Integration service to build and run tests under
# Windows
environment:
global:
PYTHON: "C:\\conda"
MINICONDA_VERSION: "latest"
OPENMP_EXPECTED: "True"
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.
# babel 2.0 is known to break on Windows:
# https://github.com/python-babel/babel/issues/174
CONDA_DEPENDENCIES: "numpy Cython sphinx pytest babel!=2.0 setuptools pytest-cov"
PIP_DEPENDENCIES: "codecov"
matrix:
- PYTHON_VERSION: "3.5"
- PYTHON_VERSION: "3.6"
- PYTHON_VERSION: "3.7"
platform:
-x64
install:
# Set up ci-helpers
- "git clone git://github.com/astropy/ci-helpers.git"
- "powershell ci-helpers/appveyor/install-miniconda.ps1"
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- "activate test"
# Some of the tests use git commands that require a user to be configured
- git config --global user.name "A U Thor"
- git config --global user.email "author@example.com"
# Not a .NET project, we build the package in the install step instead
build: false
test_script:
# Specify basetemp explicitly otherwise some of the filenames exceed
# 260 characters when fetch_build_eggs runs.
- "%CMD_IN_ENV% py.test --cov --basetemp=\\pytest-tmp astropy_helpers"
- "%CMD_IN_ENV% codecov"

Sorry, the diff of this file is not supported yet

Metadata-Version: 1.2
Name: astropy-helpers
Version: 3.1
Summary: Utilities for building and installing Astropy, Astropy affiliated packages, and their respective documentation.
Home-page: https://github.com/astropy/astropy-helpers
Author: The Astropy Developers
Author-email: astropy.team@gmail.com
License: BSD
Description: astropy-helpers
===============
.. image:: https://travis-ci.org/astropy/astropy-helpers.svg
:target: https://travis-ci.org/astropy/astropy-helpers
.. image:: https://ci.appveyor.com/api/projects/status/rt9161t9mhx02xp7/branch/master?svg=true
:target: https://ci.appveyor.com/project/Astropy/astropy-helpers
.. image:: https://codecov.io/gh/astropy/astropy-helpers/branch/master/graph/badge.svg
:target: https://codecov.io/gh/astropy/astropy-helpers
**Warning:** Please note that version ``v3.0`` and later of ``astropy-helpers``
requires Python 3.5 or later. If you wish to maintain Python 2 support
for your package that uses ``astropy-helpers``, then do not upgrade the
helpers to ``v3.0+``. We will still provide Python 2.7 compatible
releases on the ``v2.0.x`` branch during the lifetime of the ``astropy``
core package LTS of ``v2.0.x``.
About
-----
This project provides a Python package, ``astropy_helpers``, which includes
many build, installation, and documentation-related tools used by the Astropy
project, but packaged separately for use by other projects that wish to
leverage this work. The motivation behind this package and details of its
implementation are in the accepted
`Astropy Proposal for Enhancement (APE) 4 <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_.
``astropy_helpers`` includes a special "bootstrap" module called
``ah_bootstrap.py`` which is intended to be used by a project's setup.py in
order to ensure that the ``astropy_helpers`` package is available for
build/installation.
As described in `APE4 <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_, the version
numbers for ``astropy_helpers`` follow the corresponding major/minor version of
the `astropy core package <http://www.astropy.org/>`_, but with an independent
sequence of micro (bugfix) version numbers. Hence, the initial release is 0.4,
in parallel with Astropy v0.4, which will be the first version of Astropy to
use ``astropy-helpers``.
For examples of how to implement ``astropy-helpers`` in a project,
see the ``setup.py`` and ``setup.cfg`` files of the
`Affiliated package template <https://github.com/astropy/package-template>`_.
What does astropy-helpers provide?
----------------------------------
Astropy-helpers' big-picture purpose is to provide customization to Python's
packaging infrastructure process in ways that the Astropy Project has found to
help simplifying the developint and releasing packages. This is primarily
build around ``setup.py`` commands, as outlined below, as well as code to help
manage version numbers and better control the build process of larger packages.
Custom setup.py commands
^^^^^^^^^^^^^^^^^^^^^^^^
The main part of astropy-helpers is to provide customized setuptools commands.
For example, in a package that uses astropy-helpers, the following command
will be available::
python setup.py build_docs
and this command is implemented in astropy-helpers. To use the custom
commands described here, add::
from astropy_helpers.setup_helpers import register_commands
to your ``setup.py`` file, then do::
cmdclassd = register_commands(NAME, VERSION, RELEASE)
where ``NAME`` is the name of your package, ``VERSION`` is the full version
string, and ``RELEASE`` is a boolean value indicating whether the version is
a stable released version (``True``) or a developer version (``False``).
Finally, pass ``cmdclassd`` to the ``setup`` function::
setup(...,
cmdclass=cmdclassd)
The commands we provide or customize are:
**python setup.py test**
This command will automatically build the package, install it to a temporary
directory, and run the tests using `pytest <http://pytest.org/>`_ on this
installed version. Note that the bulk of this command is actually defined
in ``astropy.tests.command.AstropyTest``, because that allows the test
machinery to operate outside a setuptools command. This, here we
simply define the custom
setuptools command.
**python setup.py sdist**
We redefine ``sdist`` to use the version from distutils rather than from
setuptools, as the setuptools version requires duplication of information
in ``MANIFEST.in``.
**python setup.py build_docs**
This command will automatically build the package, then run sphinx to build
the documentation. This makes development much easier because it ensures
sphinx extensions that use the package's code to make documentation are
actually using the in-development version of the code. Sphinx itself
provides a custom setuptools command, which we
expand with the following options:
* ``-w``: set the return code to 1 if there are any warnings during the build
process.
* ``-l``: completely clean previous builds, including files generated by
the sphinx-automodapi package (which creates API pages for different
functions/classes).
* ``-n``: disable the intersphinx option.
* ``-o``: open the documentation in a browser if a build finishes successfully.
In addition, ``build_docs`` will automatically download and temporarily install
sphinx-astropy (which is a meta-package that
provides standardized configuration and documentation dependencies for astropy
packages) if it isn't already installed. Temporary installation means that the
package will be installed into an ``.eggs`` directory in the current working
directory, and it will only be available for the duration of the call to
``build_docs``.
**python setup.py build_ext**
This is also used when running ``build`` or ``install``. We add several features
compared to the default ``build_ext`` command:
* For packages with C/Cython extensions, we create a ``packagename._compiler``
submodule that contains information about the compilers used.
* Packages that need to build C extensions using the Numpy C API, we allow
those packages to define the include path as ``'numpy'`` as opposed to having
to import Numpy and call ``get_include``. The goal is to solve the issue that
if one has to import Numpy to define extensions, then Numpy has to be
installed/available before the package is installed, which means that one
needs to install Numpy in a separate installation step.
* We detect broken compilers and replace them with other compilers on-the-fly
unless the compiler is explicitly specified with the ``CC`` environment
variable.
* If Cython is not installed, then we automatically check for generated C files
(which are normally present in the stable releases) and give a nice error
if these are not found.
Version helpers
^^^^^^^^^^^^^^^^
Another piece of functionality we provide in astropy-helpers is the ability
to generate a ``packagename.version`` file that includes functions that
automatically set the version string for developer versions, to e.g.
``3.2.dev22213`` so that each developer version has a unique number (although
note that branches an equal number of commits away from the master branch will
share the same version number). To use this, import::
from astropy_helpers.git_helpers import get_git_devstr
in your ``setup.py`` file, and you will then be able to use::
VERSION += get_git_devstr()
where ``VERSION`` is a version string without any developer version suffix.
We then also provide a function that generates a ``version.py`` file inside your
package (which can then be imported as ``packagename.version``) that contains
variables such as ``major``, ``minor``, and ``bugfix``, as well as
``version_info`` (a tuple of the previous three values), a ``release`` flag that
indicates whether we are using a stable release, and several other complementary
variables. To use this, import::
from astropy_helpers.version_helpers import generate_version_py
in your ``setup.py`` file, and call::
generate_version_py(NAME, VERSION, RELEASE, uses_git=not RELEASE)
where ``NAME`` is the name of your package, ``VERSION`` is the full version string
(including any developer suffix), ``RELEASE`` indicates whether the version is a
stable or developer version, and ``uses_git`` indicates whether we are in a git
repository (using ``not RELEASE`` is sensible since git is not available in a
stable release).
Collecting package information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``setup`` function from setuptools can take a number of options that indicate
for example what extensions to build, and what package data to include. However,
for large packages this can become cumbersome. We therefore provide a mechanism
for defining extensions and package data inside individual sub-packages. To do
this, you can create ``setup_package.py`` files anywhere in your package, and
these files can include one or more of the following functions:
* ``get_package_data``:
This function, if defined, should return a dictionary mapping the name of
the subpackage(s) that need package data to a list of data file paths
(possibly including wildcards) relative to the path of the package's source
code. e.g. if the source distribution has a needed data file
``astropy/wcs/tests/data/3d_cd.hdr``, this function should return
``{'astropy.wcs.tests':['data/3d_cd.hdr']}``. See the ``package_data``
option of the :func:`distutils.core.setup` function.
It is recommended that all such data be in a directory named ``data`` inside
the package within which it is supposed to be used. This package data
should be accessed via the ``astropy.utils.data.get_pkg_data_filename`` and
``astropy.utils.data.get_pkg_data_fileobj`` functions.
* ``get_extensions``:
This provides information for building C or Cython extensions. If defined,
it should return a list of ``distutils.core.Extension`` objects.
* ``get_build_options``:
This function allows a package to add extra build options. It
should return a list of tuples, where each element has:
- *name*: The name of the option as it would appear on the
commandline or in the ``setup.cfg`` file.
- *doc*: A short doc string for the option, displayed by
``setup.py build --help``.
- *is_bool* (optional): When `True`, the option is a boolean
option and doesn't have an associated value.
Once an option has been added, its value can be looked up using
``astropy_helpers.setup_helpers.get_distutils_build_option``.
* ``get_external_libraries``:
This function declares that the package uses libraries that are
included in the astropy distribution that may also be distributed
elsewhere on the users system. It should return a list of library
names. For each library, a new build option is created,
``'--use-system-X'`` which allows the user to request to use the
system's copy of the library. The package would typically call
``astropy_helpers.setup_helpers.use_system_library`` from its
``get_extensions`` function to determine if the package should use
the system library or the included one.
* ``get_entry_points()``:
This function can returns a dict formatted as required by
the ``entry_points`` argument to ``setup()``.
With these files in place, you can then add the following to your ``setup.py``
file::
from astropy_helpers.setup_helpers import get_package_info
...
package_info = get_package_info()
...
setup(..., **package_info)
OpenMP helpers
^^^^^^^^^^^^^^
We provide a helper function ``add_openmp_flags_if_available`` that can be used
to automatically add OpenMP flags for C/Cython extensions, based on whether
OpenMP is available and produces executable code. To use this, edit the
``setup_package.py`` file where you define a C extension, import the helper
function::
from astropy_helpers.openmp_helpers import add_openmp_flags_if_available
then once you have defined the extension and before returning it, use it as::
extension = Extension(...)
add_openmp_flags_if_available(extension)
return [extension]
Using astropy-helpers
---------------------
The easiest way to get set up with astropy-helpers in a new package is to use
the `package-template <http://docs.astropy.org/projects/package-template>`_
that we provide. This template is specifically designed for use with the helpers,
so using it avoids some of the tedium of setting up the heleprs.
However, we now go through the steps of adding astropy-helpers
as a submodule to a package in case you wish to do so manually. First, add
astropy-helpers as a submodule at the root of your repository::
git submodule add git://github.com/astropy/astropy-helpers astropy_helpers
Then go inside the submodule and check out a stable version of astropy-helpers.
You can see the available versions by running::
$ cd astropy_helpers
$ git tag
...
v2.0.6
v2.0.7
...
v3.0.1
v3.0.2
If you want to support Python 2, pick the latest v2.0.x version (in the above
case ``v2.0.7``) and if you don't need to support Python 2, just pick the latest
stable version (in the above case ``v3.0.2``). Check out this version with e.g.::
$ git checkout v3.0.2
Then go back up to the root of your repository and copy the ``ah_bootstrap.py``
file from the submodule to the root of your repository::
$ cd ..
$ cp astropy_helpers/ah_bootstrap.py .
Finally, add::
import ah_bootstrap
at the top of your ``setup.py`` file. This will ensure that ``astropy_helpers``
is now available to use in your ``setup.py`` file. Finally, add then commit your
changes::
git add astropy_helpers ah_bootstrap.py setup.py
git commit -m "Added astropy-helpers"
Updating astropy-helpers
------------------------
If you would like the Astropy team to automatically open pull requests to update
astropy-helpers in your package, then see the instructions `here
<https://github.com/astropy/astropy-procedures/blob/master/update-packages/README.md>`_.
To instead update astropy-helpers manually, go inside the submodule and do::
cd astropy_helpers
git fetch origin
Then checkout the version you want to use, e.g.::
git checkout v3.0.3
Go back up to the root of the repository and update the ``ah_bootstap.py`` file
too, then add your changes::
cp astropy_helpers/ah_bootstrap.py .
git add astropy_helpers ah_bootstrap.py
git commit ...
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Framework :: Setuptools Plugin
Classifier: Framework :: Sphinx :: Extension
Classifier: Framework :: Sphinx :: Theme
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Archiving :: Packaging
Requires-Python: >=3.5
CHANGES.rst
LICENSE.rst
MANIFEST.in
README.rst
ah_bootstrap.py
setup.cfg
setup.py
astropy_helpers/__init__.py
astropy_helpers/conftest.py
astropy_helpers/distutils_helpers.py
astropy_helpers/git_helpers.py
astropy_helpers/openmp_helpers.py
astropy_helpers/setup_helpers.py
astropy_helpers/utils.py
astropy_helpers/version.py
astropy_helpers/version_helpers.py
astropy_helpers.egg-info/PKG-INFO
astropy_helpers.egg-info/SOURCES.txt
astropy_helpers.egg-info/dependency_links.txt
astropy_helpers.egg-info/not-zip-safe
astropy_helpers.egg-info/top_level.txt
astropy_helpers/commands/__init__.py
astropy_helpers/commands/_dummy.py
astropy_helpers/commands/build_ext.py
astropy_helpers/commands/build_sphinx.py
astropy_helpers/commands/setup_package.py
astropy_helpers/commands/test.py
astropy_helpers/commands/src/compiler.c
astropy_helpers/sphinx/__init__.py
astropy_helpers/sphinx/conf.py
licenses/LICENSE_ASTROSCRAPPY.rst
try:
from .version import version as __version__
from .version import githash as __githash__
except ImportError:
__version__ = ''
__githash__ = ''
# If we've made it as far as importing astropy_helpers, we don't need
# ah_bootstrap in sys.modules anymore. Getting rid of it is actually necessary
# if the package we're installing has a setup_requires of another package that
# uses astropy_helpers (and possibly a different version at that)
# See https://github.com/astropy/astropy/issues/3541
import sys
if 'ah_bootstrap' in sys.modules:
del sys.modules['ah_bootstrap']
# Note, this is repeated from ah_bootstrap.py, but is here too in case this
# astropy-helpers was upgraded to from an older version that did not have this
# check in its ah_bootstrap.
# 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
import os
# Ensure that all module-level code in astropy or other packages know that
# we're in setup mode:
if ('__main__' in sys.modules and
hasattr(sys.modules['__main__'], '__file__')):
filename = os.path.basename(sys.modules['__main__'].__file__)
if filename.rstrip('co') == 'setup.py':
import builtins
builtins._ASTROPY_SETUP_ = True
del filename
"""
Provides a base class for a 'dummy' setup.py command that has no functionality
(probably due to a missing requirement). This dummy command can raise an
exception when it is run, explaining to the user what dependencies must be met
to use this command.
The reason this is at all tricky is that we want the command to be able to
provide this message even when the user passes arguments to the command. If we
don't know ahead of time what arguments the command can take, this is
difficult, because distutils does not allow unknown arguments to be passed to a
setup.py command. This hacks around that restriction to provide a useful error
message even when a user passes arguments to the dummy implementation of a
command.
Use this like:
try:
from some_dependency import SetupCommand
except ImportError:
from ._dummy import _DummyCommand
class SetupCommand(_DummyCommand):
description = \
'Implementation of SetupCommand from some_dependency; '
'some_dependency must be installed to run this command'
# This is the message that will be raised when a user tries to
# run this command--define it as a class attribute.
error_msg = \
"The 'setup_command' command requires the some_dependency "
"package to be installed and importable."
"""
import sys
from setuptools import Command
from distutils.errors import DistutilsArgError
from textwrap import dedent
class _DummyCommandMeta(type):
"""
Causes an exception to be raised on accessing attributes of a command class
so that if ``./setup.py command_name`` is run with additional command-line
options we can provide a useful error message instead of the default that
tells users the options are unrecognized.
"""
def __init__(cls, name, bases, members):
if bases == (Command, object):
# This is the _DummyCommand base class, presumably
return
if not hasattr(cls, 'description'):
raise TypeError(
"_DummyCommand subclass must have a 'description' "
"attribute.")
if not hasattr(cls, 'error_msg'):
raise TypeError(
"_DummyCommand subclass must have an 'error_msg' "
"attribute.")
def __getattribute__(cls, attr):
if attr in ('description', 'error_msg'):
# Allow cls.description to work so that `./setup.py
# --help-commands` still works
return super(_DummyCommandMeta, cls).__getattribute__(attr)
raise DistutilsArgError(cls.error_msg)
class _DummyCommand(Command, object, metaclass=_DummyCommandMeta):
pass
import errno
import os
import re
import shlex
import shutil
import subprocess
import sys
import textwrap
from distutils import log, ccompiler, sysconfig
from distutils.core import Extension
from distutils.ccompiler import get_default_compiler
from importlib import invalidate_caches
from setuptools.command.build_ext import build_ext as SetuptoolsBuildExt
from setuptools.command import build_py
from ..utils import get_numpy_include_path, classproperty
from ..version_helpers import get_pkg_version_module
def should_build_with_cython(package, release=None):
"""Returns the previously used Cython version (or 'unknown' if not
previously built) if Cython should be used to build extension modules from
pyx files. If the ``release`` parameter is not specified an attempt is
made to determine the release flag from `astropy.version`.
"""
try:
version_module = __import__(package + '.cython_version',
fromlist=['release', 'cython_version'])
except ImportError:
version_module = None
if release is None and version_module is not None:
try:
release = version_module.release
except AttributeError:
pass
try:
cython_version = version_module.cython_version
except AttributeError:
cython_version = 'unknown'
# Only build with Cython if, of course, Cython is installed, we're in a
# development version (i.e. not release) or the Cython-generated source
# files haven't been created yet (cython_version == 'unknown'). The latter
# case can happen even when release is True if checking out a release tag
# from the repository
have_cython = False
try:
import Cython # noqa
have_cython = True
except ImportError:
pass
if have_cython and (not release or cython_version == 'unknown'):
return cython_version
else:
return False
_compiler_versions = {}
def get_compiler_version(compiler):
if compiler in _compiler_versions:
return _compiler_versions[compiler]
# Different flags to try to get the compiler version
# TODO: It might be worth making this configurable to support
# arbitrary odd compilers; though all bets may be off in such
# cases anyway
flags = ['--version', '--Version', '-version', '-Version',
'-v', '-V']
def try_get_version(flag):
process = subprocess.Popen(
shlex.split(compiler, posix=('win' not in sys.platform)) + [flag],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode != 0:
return 'unknown'
output = stdout.strip().decode('latin-1') # Safest bet
if not output:
# Some compilers return their version info on stderr
output = stderr.strip().decode('latin-1')
if not output:
output = 'unknown'
return output
for flag in flags:
version = try_get_version(flag)
if version != 'unknown':
break
# Cache results to speed up future calls
_compiler_versions[compiler] = version
return version
# TODO: I think this can be reworked without having to create the class
# programmatically.
def generate_build_ext_command(packagename, release):
"""
Creates a custom 'build_ext' command that allows for manipulating some of
the C extension options at build time. We use a function to build the
class since the base class for build_ext may be different depending on
certain build-time parameters (for example, we may use Cython's build_ext
instead of the default version in distutils).
Uses the default distutils.command.build_ext by default.
"""
class build_ext(SetuptoolsBuildExt, object):
package_name = packagename
is_release = release
_user_options = SetuptoolsBuildExt.user_options[:]
_boolean_options = SetuptoolsBuildExt.boolean_options[:]
_help_options = SetuptoolsBuildExt.help_options[:]
force_rebuild = False
_broken_compiler_mapping = [
('i686-apple-darwin[0-9]*-llvm-gcc-4.2', 'clang')
]
# Warning: Spaghetti code ahead.
# During setup.py, the setup_helpers module needs the ability to add
# items to a command's user_options list. At this stage we don't know
# whether or not we can build with Cython, and so don't know for sure
# what base class will be used for build_ext; nevertheless we want to
# be able to provide a list to add options into.
#
# Later, once setup() has been called we should have all build
# dependencies included via setup_requires available. distutils needs
# to be able to access the user_options as a *class* attribute before
# the class has been initialized, but we do need to be able to
# enumerate the options for the correct base class at that point
@classproperty
def user_options(cls):
from distutils import core
if core._setup_distribution is None:
# We haven't gotten into setup() yet, and the Distribution has
# not yet been initialized
return cls._user_options
return cls._final_class.user_options
@classproperty
def boolean_options(cls):
# Similar to user_options above
from distutils import core
if core._setup_distribution is None:
# We haven't gotten into setup() yet, and the Distribution has
# not yet been initialized
return cls._boolean_options
return cls._final_class.boolean_options
@classproperty
def help_options(cls):
# Similar to user_options above
from distutils import core
if core._setup_distribution is None:
# We haven't gotten into setup() yet, and the Distribution has
# not yet been initialized
return cls._help_options
return cls._final_class.help_options
@classproperty(lazy=True)
def _final_class(cls):
"""
Late determination of what the build_ext base class should be,
depending on whether or not Cython is available.
"""
uses_cython = should_build_with_cython(cls.package_name,
cls.is_release)
if uses_cython:
# We need to decide late on whether or not to use Cython's
# build_ext (since Cython may not be available earlier in the
# setup.py if it was brought in via setup_requires)
try:
from Cython.Distutils.old_build_ext import old_build_ext as base_cls
except ImportError:
from Cython.Distutils import build_ext as base_cls
else:
base_cls = SetuptoolsBuildExt
# Create and return an instance of a new class based on this class
# using one of the above possible base classes
def merge_options(attr):
base = getattr(base_cls, attr)
ours = getattr(cls, '_' + attr)
all_base = set(opt[0] for opt in base)
return base + [opt for opt in ours if opt[0] not in all_base]
boolean_options = (base_cls.boolean_options +
[opt for opt in cls._boolean_options
if opt not in base_cls.boolean_options])
members = dict(cls.__dict__)
members.update({
'user_options': merge_options('user_options'),
'help_options': merge_options('help_options'),
'boolean_options': boolean_options,
'uses_cython': uses_cython,
})
# Update the base class for the original build_ext command
build_ext.__bases__ = (base_cls, object)
# Create a new class for the existing class, but now with the
# appropriate base class depending on whether or not to use Cython.
# Ensure that object is one of the bases to make a new-style class.
return type(cls.__name__, (build_ext,), members)
def __new__(cls, *args, **kwargs):
# By the time the command is actually instantialized, the
# Distribution instance for the build has been instantiated, which
# means setup_requires has been processed--now we can determine
# what base class we can use for the actual build, and return an
# instance of a build_ext command that uses that base class (right
# now the options being Cython.Distutils.build_ext, or the stock
# setuptools build_ext)
new_cls = super(build_ext, cls._final_class).__new__(cls._final_class)
# Since the new cls is not a subclass of the original cls, we must
# manually call its __init__
new_cls.__init__(*args, **kwargs)
return new_cls
def finalize_options(self):
# Add a copy of the _compiler.so module as well, but only if there
# are in fact C modules to compile (otherwise there's no reason to
# include a record of the compiler used)
# Note, self.extensions may not be set yet, but
# self.distribution.ext_modules is where any extension modules
# passed to setup() can be found
self._adjust_compiler()
extensions = self.distribution.ext_modules
if extensions:
build_py = self.get_finalized_command('build_py')
package_dir = build_py.get_package_dir(packagename)
src_path = os.path.relpath(
os.path.join(os.path.dirname(__file__), 'src'))
shutil.copy(os.path.join(src_path, 'compiler.c'),
os.path.join(package_dir, '_compiler.c'))
ext = Extension(self.package_name + '._compiler',
[os.path.join(package_dir, '_compiler.c')])
extensions.insert(0, ext)
super(build_ext, self).finalize_options()
# Generate
if self.uses_cython:
try:
from Cython import __version__ as cython_version
except ImportError:
# This shouldn't happen if we made it this far
cython_version = None
if (cython_version is not None and
cython_version != self.uses_cython):
self.force_rebuild = True
# Update the used cython version
self.uses_cython = cython_version
# Regardless of the value of the '--force' option, force a rebuild
# if the debug flag changed from the last build
if self.force_rebuild:
self.force = True
def run(self):
# For extensions that require 'numpy' in their include dirs,
# replace 'numpy' with the actual paths
np_include = None
for extension in self.extensions:
if 'numpy' in extension.include_dirs:
if np_include is None:
np_include = get_numpy_include_path()
idx = extension.include_dirs.index('numpy')
extension.include_dirs.insert(idx, np_include)
extension.include_dirs.remove('numpy')
self._check_cython_sources(extension)
super(build_ext, self).run()
# Update cython_version.py if building with Cython
try:
cython_version = get_pkg_version_module(
packagename, fromlist=['cython_version'])[0]
except (AttributeError, ImportError):
cython_version = 'unknown'
if self.uses_cython and self.uses_cython != cython_version:
build_py = self.get_finalized_command('build_py')
package_dir = build_py.get_package_dir(packagename)
cython_py = os.path.join(package_dir, 'cython_version.py')
with open(cython_py, 'w') as f:
f.write('# Generated file; do not modify\n')
f.write('cython_version = {0!r}\n'.format(self.uses_cython))
if os.path.isdir(self.build_lib):
# The build/lib directory may not exist if the build_py
# command was not previously run, which may sometimes be
# the case
self.copy_file(cython_py,
os.path.join(self.build_lib, cython_py),
preserve_mode=False)
invalidate_caches()
def _adjust_compiler(self):
"""
This function detects broken compilers and switches to another. If
the environment variable CC is explicitly set, or a compiler is
specified on the commandline, no override is performed -- the
purpose here is to only override a default compiler.
The specific compilers with problems are:
* The default compiler in XCode-4.2, llvm-gcc-4.2,
segfaults when compiling wcslib.
The set of broken compilers can be updated by changing the
compiler_mapping variable. It is a list of 2-tuples where the
first in the pair is a regular expression matching the version of
the broken compiler, and the second is the compiler to change to.
"""
if 'CC' in os.environ:
# Check that CC is not set to llvm-gcc-4.2
c_compiler = os.environ['CC']
try:
version = get_compiler_version(c_compiler)
except OSError:
msg = textwrap.dedent(
"""
The C compiler set by the CC environment variable:
{compiler:s}
cannot be found or executed.
""".format(compiler=c_compiler))
log.warn(msg)
sys.exit(1)
for broken, fixed in self._broken_compiler_mapping:
if re.match(broken, version):
msg = textwrap.dedent(
"""Compiler specified by CC environment variable
({compiler:s}:{version:s}) will fail to compile
{pkg:s}.
Please set CC={fixed:s} and try again.
You can do this, for example, by running:
CC={fixed:s} python setup.py <command>
where <command> is the command you ran.
""".format(compiler=c_compiler, version=version,
pkg=self.package_name, fixed=fixed))
log.warn(msg)
sys.exit(1)
# If C compiler is set via CC, and isn't broken, we are good to go. We
# should definitely not try accessing the compiler specified by
# ``sysconfig.get_config_var('CC')`` lower down, because this may fail
# if the compiler used to compile Python is missing (and maybe this is
# why the user is setting CC). For example, the official Python 2.7.3
# MacOS X binary was compiled with gcc-4.2, which is no longer available
# in XCode 4.
return
if self.compiler is not None:
# At this point, self.compiler will be set only if a compiler
# was specified in the command-line or via setup.cfg, in which
# case we don't do anything
return
compiler_type = ccompiler.get_default_compiler()
if compiler_type == 'unix':
# We have to get the compiler this way, as this is the one that is
# used if os.environ['CC'] is not set. It is actually read in from
# the Python Makefile. Note that this is not necessarily the same
# compiler as returned by ccompiler.new_compiler()
c_compiler = sysconfig.get_config_var('CC')
try:
version = get_compiler_version(c_compiler)
except OSError:
msg = textwrap.dedent(
"""
The C compiler used to compile Python {compiler:s}, and
which is normally used to compile C extensions, is not
available. You can explicitly specify which compiler to
use by setting the CC environment variable, for example:
CC=gcc python setup.py <command>
or if you are using MacOS X, you can try:
CC=clang python setup.py <command>
""".format(compiler=c_compiler))
log.warn(msg)
sys.exit(1)
for broken, fixed in self._broken_compiler_mapping:
if re.match(broken, version):
os.environ['CC'] = fixed
break
def _check_cython_sources(self, extension):
"""
Where relevant, make sure that the .c files associated with .pyx
modules are present (if building without Cython installed).
"""
# Determine the compiler we'll be using
if self.compiler is None:
compiler = get_default_compiler()
else:
compiler = self.compiler
# Replace .pyx with C-equivalents, unless c files are missing
for jdx, src in enumerate(extension.sources):
base, ext = os.path.splitext(src)
pyxfn = base + '.pyx'
cfn = base + '.c'
cppfn = base + '.cpp'
if not os.path.isfile(pyxfn):
continue
if self.uses_cython:
extension.sources[jdx] = pyxfn
else:
if os.path.isfile(cfn):
extension.sources[jdx] = cfn
elif os.path.isfile(cppfn):
extension.sources[jdx] = cppfn
else:
msg = (
'Could not find C/C++ file {0}.(c/cpp) for Cython '
'file {1} when building extension {2}. Cython '
'must be installed to build from a git '
'checkout.'.format(base, pyxfn, extension.name))
raise IOError(errno.ENOENT, msg, cfn)
# Current versions of Cython use deprecated Numpy API features
# the use of which produces a few warnings when compiling.
# These additional flags should squelch those warnings.
# TODO: Feel free to remove this if/when a Cython update
# removes use of the deprecated Numpy API
if compiler == 'unix':
extension.extra_compile_args.extend([
'-Wp,-w', '-Wno-unused-function'])
return build_ext
from __future__ import print_function
import os
import pkgutil
import re
import shutil
import subprocess
import sys
import glob
import warnings
from distutils.version import LooseVersion
from distutils import log
from sphinx import __version__ as sphinx_version
from sphinx.setup_command import BuildDoc as SphinxBuildDoc
from ..utils import AstropyDeprecationWarning
SPHINX_LT_17 = LooseVersion(sphinx_version) < LooseVersion('1.7')
SUBPROCESS_TEMPLATE = """
import os
import sys
{build_main}
os.chdir({srcdir!r})
{sys_path_inserts}
for builder in {builders!r}:
retcode = build_main(argv={argv!r} + ['-b', builder, '.', os.path.join({output_dir!r}, builder)])
if retcode != 0:
sys.exit(retcode)
"""
def ensure_sphinx_astropy_installed():
"""
Make sure that sphinx-astropy is available, installing it temporarily if not.
This returns the available version of sphinx-astropy as well as any
paths that should be added to sys.path for sphinx-astropy to be available.
"""
# We've split out the Sphinx part of astropy-helpers into sphinx-astropy
# but we want it to be auto-installed seamlessly for anyone using
# build_docs. We check if it's already installed, and if not, we install
# it to a local .eggs directory and add the eggs to the path (these
# have to each be added to the path, we can't add them by simply adding
# .eggs to the path)
sys_path_inserts = []
sphinx_astropy_version = None
try:
from sphinx_astropy import __version__ as sphinx_astropy_version # noqa
except ImportError:
from setuptools import Distribution
dist = Distribution()
eggs = dist.fetch_build_eggs('sphinx-astropy')
# Find out the version of sphinx-astropy if possible. For some old
# setuptools version, eggs will be None even if sphinx-astropy was
# successfully installed.
if eggs is not None:
for egg in eggs:
if egg.project_name == 'sphinx-astropy':
sphinx_astropy_version = egg.parsed_version.public
break
eggs_path = os.path.abspath('.eggs')
for egg in glob.glob(os.path.join(eggs_path, '*.egg')):
sys_path_inserts.append(egg)
return sphinx_astropy_version, sys_path_inserts
class AstropyBuildDocs(SphinxBuildDoc):
"""
A version of the ``build_docs`` command that uses the version of Astropy
that is built by the setup ``build`` command, rather than whatever is
installed on the system. To build docs against the installed version, run
``make html`` in the ``astropy/docs`` directory.
"""
description = 'Build Sphinx documentation for Astropy environment'
user_options = SphinxBuildDoc.user_options[:]
user_options.append(
('warnings-returncode', 'w',
'Parses the sphinx output and sets the return code to 1 if there '
'are any warnings. Note that this will cause the sphinx log to '
'only update when it completes, rather than continuously as is '
'normally the case.'))
user_options.append(
('clean-docs', 'l',
'Completely clean previous builds, including '
'automodapi-generated files before building new ones'))
user_options.append(
('no-intersphinx', 'n',
'Skip intersphinx, even if conf.py says to use it'))
user_options.append(
('open-docs-in-browser', 'o',
'Open the docs in a browser (using the webbrowser module) if the '
'build finishes successfully.'))
boolean_options = SphinxBuildDoc.boolean_options[:]
boolean_options.append('warnings-returncode')
boolean_options.append('clean-docs')
boolean_options.append('no-intersphinx')
boolean_options.append('open-docs-in-browser')
_self_iden_rex = re.compile(r"self\.([^\d\W][\w]+)", re.UNICODE)
def initialize_options(self):
SphinxBuildDoc.initialize_options(self)
self.clean_docs = False
self.no_intersphinx = False
self.open_docs_in_browser = False
self.warnings_returncode = False
self.traceback = False
def finalize_options(self):
# This has to happen before we call the parent class's finalize_options
if self.build_dir is None:
self.build_dir = 'docs/_build'
SphinxBuildDoc.finalize_options(self)
# Clear out previous sphinx builds, if requested
if self.clean_docs:
dirstorm = [os.path.join(self.source_dir, 'api'),
os.path.join(self.source_dir, 'generated')]
dirstorm.append(self.build_dir)
for d in dirstorm:
if os.path.isdir(d):
log.info('Cleaning directory ' + d)
shutil.rmtree(d)
else:
log.info('Not cleaning directory ' + d + ' because '
'not present or not a directory')
def run(self):
# TODO: Break this method up into a few more subroutines and
# document them better
import webbrowser
from urllib.request import pathname2url
# This is used at the very end of `run` to decide if sys.exit should
# be called. If it's None, it won't be.
retcode = None
# Now make sure Astropy is built and determine where it was built
build_cmd = self.reinitialize_command('build')
build_cmd.inplace = 0
self.run_command('build')
build_cmd = self.get_finalized_command('build')
build_cmd_path = os.path.abspath(build_cmd.build_lib)
ah_importer = pkgutil.get_importer('astropy_helpers')
if ah_importer is None:
ah_path = '.'
else:
ah_path = os.path.abspath(ah_importer.path)
if SPHINX_LT_17:
build_main = 'from sphinx import build_main'
else:
build_main = 'from sphinx.cmd.build import build_main'
# We need to make sure sphinx-astropy is installed and install it
# temporarily if not
sphinx_astropy_version, extra_paths = ensure_sphinx_astropy_installed()
sys_path_inserts = [build_cmd_path, ah_path] + extra_paths
sys_path_inserts = os.linesep.join(['sys.path.insert(0, {0!r})'.format(path) for path in sys_path_inserts])
argv = []
if self.warnings_returncode:
argv.append('-W')
if self.no_intersphinx:
# Note, if sphinx_astropy_version is None, this could indicate an
# old version of setuptools, but sphinx-astropy is likely ok, so
# we can proceed.
if sphinx_astropy_version is None or LooseVersion(sphinx_astropy_version) >= LooseVersion('1.1'):
argv.extend(['-D', 'disable_intersphinx=1'])
else:
log.warn('The -n option to disable intersphinx requires '
'sphinx-astropy>=1.1. Ignoring.')
# We now need to adjust the flags based on the parent class's options
if self.fresh_env:
argv.append('-E')
if self.all_files:
argv.append('-a')
if getattr(self, 'pdb', False):
argv.append('-P')
if getattr(self, 'nitpicky', False):
argv.append('-n')
if self.traceback:
argv.append('-T')
# The default verbosity level is 1, so in that case we just don't add a flag
if self.verbose == 0:
argv.append('-q')
elif self.verbose > 1:
argv.append('-v')
if SPHINX_LT_17:
argv.insert(0, 'sphinx-build')
if isinstance(self.builder, str):
builders = [self.builder]
else:
builders = self.builder
subproccode = SUBPROCESS_TEMPLATE.format(build_main=build_main,
srcdir=self.source_dir,
sys_path_inserts=sys_path_inserts,
builders=builders,
argv=argv,
output_dir=os.path.abspath(self.build_dir))
log.debug('Starting subprocess of {0} with python code:\n{1}\n'
'[CODE END])'.format(sys.executable, subproccode))
proc = subprocess.Popen([sys.executable], stdin=subprocess.PIPE)
proc.communicate(subproccode.encode('utf-8'))
if proc.returncode != 0:
retcode = proc.returncode
if retcode is None:
if self.open_docs_in_browser:
if self.builder == 'html':
absdir = os.path.abspath(self.builder_target_dir)
index_path = os.path.join(absdir, 'index.html')
fileurl = 'file://' + pathname2url(index_path)
webbrowser.open(fileurl)
else:
log.warn('open-docs-in-browser option was given, but '
'the builder is not html! Ignoring.')
# Here we explicitly check proc.returncode since we only want to output
# this for cases where the return code really wasn't 0.
if proc.returncode:
log.warn('Sphinx Documentation subprocess failed with return '
'code ' + str(proc.returncode))
if retcode is not None:
# this is potentially dangerous in that there might be something
# after the call to `setup` in `setup.py`, and exiting here will
# prevent that from running. But there's no other apparent way
# to signal what the return code should be.
sys.exit(retcode)
class AstropyBuildSphinx(AstropyBuildDocs): # pragma: no cover
description = 'deprecated alias to the build_docs command'
def run(self):
warnings.warn(
'The "build_sphinx" command is now deprecated. Use'
'"build_docs" instead.', AstropyDeprecationWarning)
AstropyBuildDocs.run(self)
from os.path import join
def get_package_data():
return {'astropy_helpers.commands': [join('src', 'compiler.c')]}
#include <Python.h>
/***************************************************************************
* Macros for determining the compiler version.
*
* These are borrowed from boost, and majorly abridged to include only
* the compilers we care about.
***************************************************************************/
#ifndef PY3K
#if PY_MAJOR_VERSION >= 3
#define PY3K 1
#else
#define PY3K 0
#endif
#endif
#define STRINGIZE(X) DO_STRINGIZE(X)
#define DO_STRINGIZE(X) #X
#if defined __clang__
/* Clang C++ emulates GCC, so it has to appear early. */
# define COMPILER "Clang version " __clang_version__
#elif defined(__INTEL_COMPILER) || defined(__ICL) || defined(__ICC) || defined(__ECC)
/* Intel */
# if defined(__INTEL_COMPILER)
# define INTEL_VERSION __INTEL_COMPILER
# elif defined(__ICL)
# define INTEL_VERSION __ICL
# elif defined(__ICC)
# define INTEL_VERSION __ICC
# elif defined(__ECC)
# define INTEL_VERSION __ECC
# endif
# define COMPILER "Intel C compiler version " STRINGIZE(INTEL_VERSION)
#elif defined(__GNUC__)
/* gcc */
# define COMPILER "GCC version " __VERSION__
#elif defined(__SUNPRO_CC)
/* Sun Workshop Compiler */
# define COMPILER "Sun compiler version " STRINGIZE(__SUNPRO_CC)
#elif defined(_MSC_VER)
/* Microsoft Visual C/C++
Must be last since other compilers define _MSC_VER for compatibility as well */
# if _MSC_VER < 1200
# define COMPILER_VERSION 5.0
# elif _MSC_VER < 1300
# define COMPILER_VERSION 6.0
# elif _MSC_VER == 1300
# define COMPILER_VERSION 7.0
# elif _MSC_VER == 1310
# define COMPILER_VERSION 7.1
# elif _MSC_VER == 1400
# define COMPILER_VERSION 8.0
# elif _MSC_VER == 1500
# define COMPILER_VERSION 9.0
# elif _MSC_VER == 1600
# define COMPILER_VERSION 10.0
# else
# define COMPILER_VERSION _MSC_VER
# endif
# define COMPILER "Microsoft Visual C++ version " STRINGIZE(COMPILER_VERSION)
#else
/* Fallback */
# define COMPILER "Unknown compiler"
#endif
/***************************************************************************
* Module-level
***************************************************************************/
struct module_state {
/* The Sun compiler can't handle empty structs */
#if defined(__SUNPRO_C) || defined(_MSC_VER)
int _dummy;
#endif
};
#if PY3K
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"_compiler",
NULL,
sizeof(struct module_state),
NULL,
NULL,
NULL,
NULL,
NULL
};
#define INITERROR return NULL
PyMODINIT_FUNC
PyInit__compiler(void)
#else
#define INITERROR return
PyMODINIT_FUNC
init_compiler(void)
#endif
{
PyObject* m;
#if PY3K
m = PyModule_Create(&moduledef);
#else
m = Py_InitModule3("_compiler", NULL, NULL);
#endif
if (m == NULL)
INITERROR;
PyModule_AddStringConstant(m, "compiler", COMPILER);
#if PY3K
return m;
#endif
}
"""
Different implementations of the ``./setup.py test`` command depending on
what's locally available.
If Astropy v1.1.0.dev or later is available it should be possible to import
AstropyTest from ``astropy.tests.command``. If ``astropy`` can be imported
but not ``astropy.tests.command`` (i.e. an older version of Astropy), we can
use the backwards-compat implementation of the command.
If Astropy can't be imported at all then there is a skeleton implementation
that allows users to at least discover the ``./setup.py test`` command and
learn that they need Astropy to run it.
"""
# Previously these except statements caught only ImportErrors, but there are
# some other obscure exceptional conditions that can occur when importing
# astropy.tests (at least on older versions) that can cause these imports to
# fail
try:
import astropy # noqa
from astropy.tests.command import AstropyTest
except Exception:
# No astropy at all--provide the dummy implementation
from ._dummy import _DummyCommand
class AstropyTest(_DummyCommand):
command_name = 'test'
description = 'Run the tests for this package'
error_msg = (
"The 'test' command requires the astropy package to be "
"installed and importable.")
# This file contains settings for pytest that are specific to astropy-helpers.
# Since we run many of the tests in sub-processes, we need to collect coverage
# data inside each subprocess and then combine it into a single .coverage file.
# To do this we set up a list which run_setup appends coverage objects to.
# This is not intended to be used by packages other than astropy-helpers.
import os
import glob
try:
from coverage import CoverageData
except ImportError:
HAS_COVERAGE = False
else:
HAS_COVERAGE = True
if HAS_COVERAGE:
SUBPROCESS_COVERAGE = []
def pytest_configure(config):
if HAS_COVERAGE:
SUBPROCESS_COVERAGE.clear()
def pytest_unconfigure(config):
if HAS_COVERAGE:
# We create an empty coverage data object
combined_cdata = CoverageData()
# Add all files from astropy_helpers to make sure we compute the total
# coverage, not just the coverage of the files that have non-zero
# coverage.
lines = {}
for filename in glob.glob(os.path.join('astropy_helpers', '**', '*.py'), recursive=True):
lines[os.path.abspath(filename)] = []
for cdata in SUBPROCESS_COVERAGE:
# For each CoverageData object, we go through all the files and
# change the filename from one which might be a temporary path
# to the local filename. We then only keep files that actually
# exist.
for filename in cdata.measured_files():
try:
pos = filename.rindex('astropy_helpers')
except ValueError:
continue
short_filename = filename[pos:]
if os.path.exists(short_filename):
lines[os.path.abspath(short_filename)].extend(cdata.lines(filename))
combined_cdata.add_lines(lines)
combined_cdata.write_file('.coverage.subprocess')
"""
This module contains various utilities for introspecting the distutils
module and the setup process.
Some of these utilities require the
`astropy_helpers.setup_helpers.register_commands` function to be called first,
as it will affect introspection of setuptools command-line arguments. Other
utilities in this module do not have that restriction.
"""
import os
import sys
from distutils import ccompiler, log
from distutils.dist import Distribution
from distutils.errors import DistutilsError
from .utils import silence
# This function, and any functions that call it, require the setup in
# `astropy_helpers.setup_helpers.register_commands` to be run first.
def get_dummy_distribution():
"""
Returns a distutils Distribution object used to instrument the setup
environment before calling the actual setup() function.
"""
from .setup_helpers import _module_state
if _module_state['registered_commands'] is None:
raise RuntimeError(
'astropy_helpers.setup_helpers.register_commands() must be '
'called before using '
'astropy_helpers.setup_helpers.get_dummy_distribution()')
# Pre-parse the Distutils command-line options and config files to if
# the option is set.
dist = Distribution({'script_name': os.path.basename(sys.argv[0]),
'script_args': sys.argv[1:]})
dist.cmdclass.update(_module_state['registered_commands'])
with silence():
try:
dist.parse_config_files()
dist.parse_command_line()
except (DistutilsError, AttributeError, SystemExit):
# Let distutils handle DistutilsErrors itself AttributeErrors can
# get raise for ./setup.py --help SystemExit can be raised if a
# display option was used, for example
pass
return dist
def get_distutils_option(option, commands):
""" Returns the value of the given distutils option.
Parameters
----------
option : str
The name of the option
commands : list of str
The list of commands on which this option is available
Returns
-------
val : str or None
the value of the given distutils option. If the option is not set,
returns None.
"""
dist = get_dummy_distribution()
for cmd in commands:
cmd_opts = dist.command_options.get(cmd)
if cmd_opts is not None and option in cmd_opts:
return cmd_opts[option][1]
else:
return None
def get_distutils_build_option(option):
""" Returns the value of the given distutils build option.
Parameters
----------
option : str
The name of the option
Returns
-------
val : str or None
The value of the given distutils build option. If the option
is not set, returns None.
"""
return get_distutils_option(option, ['build', 'build_ext', 'build_clib'])
def get_distutils_install_option(option):
""" Returns the value of the given distutils install option.
Parameters
----------
option : str
The name of the option
Returns
-------
val : str or None
The value of the given distutils build option. If the option
is not set, returns None.
"""
return get_distutils_option(option, ['install'])
def get_distutils_build_or_install_option(option):
""" Returns the value of the given distutils build or install option.
Parameters
----------
option : str
The name of the option
Returns
-------
val : str or None
The value of the given distutils build or install option. If the
option is not set, returns None.
"""
return get_distutils_option(option, ['build', 'build_ext', 'build_clib',
'install'])
def get_compiler_option():
""" Determines the compiler that will be used to build extension modules.
Returns
-------
compiler : str
The compiler option specified for the build, build_ext, or build_clib
command; or the default compiler for the platform if none was
specified.
"""
compiler = get_distutils_build_option('compiler')
if compiler is None:
return ccompiler.get_default_compiler()
return compiler
def add_command_option(command, name, doc, is_bool=False):
"""
Add a custom option to a setup command.
Issues a warning if the option already exists on that command.
Parameters
----------
command : str
The name of the command as given on the command line
name : str
The name of the build option
doc : str
A short description of the option, for the `--help` message
is_bool : bool, optional
When `True`, the option is a boolean option and doesn't
require an associated value.
"""
dist = get_dummy_distribution()
cmdcls = dist.get_command_class(command)
if (hasattr(cmdcls, '_astropy_helpers_options') and
name in cmdcls._astropy_helpers_options):
return
attr = name.replace('-', '_')
if hasattr(cmdcls, attr):
raise RuntimeError(
'{0!r} already has a {1!r} class attribute, barring {2!r} from '
'being usable as a custom option name.'.format(cmdcls, attr, name))
for idx, cmd in enumerate(cmdcls.user_options):
if cmd[0] == name:
log.warn('Overriding existing {0!r} option '
'{1!r}'.format(command, name))
del cmdcls.user_options[idx]
if name in cmdcls.boolean_options:
cmdcls.boolean_options.remove(name)
break
cmdcls.user_options.append((name, None, doc))
if is_bool:
cmdcls.boolean_options.append(name)
# Distutils' command parsing requires that a command object have an
# attribute with the same name as the option (with '-' replaced with '_')
# in order for that option to be recognized as valid
setattr(cmdcls, attr, None)
# This caches the options added through add_command_option so that if it is
# run multiple times in the same interpreter repeated adds are ignored
# (this way we can still raise a RuntimeError if a custom option overrides
# a built-in option)
if not hasattr(cmdcls, '_astropy_helpers_options'):
cmdcls._astropy_helpers_options = set([name])
else:
cmdcls._astropy_helpers_options.add(name)
def get_distutils_display_options():
""" Returns a set of all the distutils display options in their long and
short forms. These are the setup.py arguments such as --name or --version
which print the project's metadata and then exit.
Returns
-------
opts : set
The long and short form display option arguments, including the - or --
"""
short_display_opts = set('-' + o[1] for o in Distribution.display_options
if o[1])
long_display_opts = set('--' + o[0] for o in Distribution.display_options)
# Include -h and --help which are not explicitly listed in
# Distribution.display_options (as they are handled by optparse)
short_display_opts.add('-h')
long_display_opts.add('--help')
# This isn't the greatest approach to hardcode these commands.
# However, there doesn't seem to be a good way to determine
# whether build *will be* run as part of the command at this
# phase.
display_commands = set([
'clean', 'register', 'setopt', 'saveopts', 'egg_info',
'alias'])
return short_display_opts.union(long_display_opts.union(display_commands))
def is_distutils_display_option():
""" Returns True if sys.argv contains any of the distutils display options
such as --version or --name.
"""
display_options = get_distutils_display_options()
return bool(set(sys.argv[1:]).intersection(display_options))
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Utilities for retrieving revision information from a project's git repository.
"""
# Do not remove the following comment; it is used by
# astropy_helpers.version_helpers to determine the beginning of the code in
# this module
# BEGIN
import locale
import os
import subprocess
import warnings
def _decode_stdio(stream):
try:
stdio_encoding = locale.getdefaultlocale()[1] or 'utf-8'
except ValueError:
stdio_encoding = 'utf-8'
try:
text = stream.decode(stdio_encoding)
except UnicodeDecodeError:
# Final fallback
text = stream.decode('latin1')
return text
def update_git_devstr(version, path=None):
"""
Updates the git revision string if and only if the path is being imported
directly from a git working copy. This ensures that the revision number in
the version string is accurate.
"""
try:
# Quick way to determine if we're in git or not - returns '' if not
devstr = get_git_devstr(sha=True, show_warning=False, path=path)
except OSError:
return version
if not devstr:
# Probably not in git so just pass silently
return version
if 'dev' in version: # update to the current git revision
version_base = version.split('.dev', 1)[0]
devstr = get_git_devstr(sha=False, show_warning=False, path=path)
return version_base + '.dev' + devstr
else:
# otherwise it's already the true/release version
return version
def get_git_devstr(sha=False, show_warning=True, path=None):
"""
Determines the number of revisions in this repository.
Parameters
----------
sha : bool
If True, the full SHA1 hash will be returned. Otherwise, the total
count of commits in the repository will be used as a "revision
number".
show_warning : bool
If True, issue a warning if git returns an error code, otherwise errors
pass silently.
path : str or None
If a string, specifies the directory to look in to find the git
repository. If `None`, the current working directory is used, and must
be the root of the git repository.
If given a filename it uses the directory containing that file.
Returns
-------
devversion : str
Either a string with the revision number (if `sha` is False), the
SHA1 hash of the current commit (if `sha` is True), or an empty string
if git version info could not be identified.
"""
if path is None:
path = os.getcwd()
if not os.path.isdir(path):
path = os.path.abspath(os.path.dirname(path))
if sha:
# Faster for getting just the hash of HEAD
cmd = ['rev-parse', 'HEAD']
else:
cmd = ['rev-list', '--count', 'HEAD']
def run_git(cmd):
try:
p = subprocess.Popen(['git'] + cmd, cwd=path,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
stdout, stderr = p.communicate()
except OSError as e:
if show_warning:
warnings.warn('Error running git: ' + str(e))
return (None, b'', b'')
if p.returncode == 128:
if show_warning:
warnings.warn('No git repository present at {0!r}! Using '
'default dev version.'.format(path))
return (p.returncode, b'', b'')
if p.returncode == 129:
if show_warning:
warnings.warn('Your git looks old (does it support {0}?); '
'consider upgrading to v1.7.2 or '
'later.'.format(cmd[0]))
return (p.returncode, stdout, stderr)
elif p.returncode != 0:
if show_warning:
warnings.warn('Git failed while determining revision '
'count: {0}'.format(_decode_stdio(stderr)))
return (p.returncode, stdout, stderr)
return p.returncode, stdout, stderr
returncode, stdout, stderr = run_git(cmd)
if not sha and returncode == 128:
# git returns 128 if the command is not run from within a git
# repository tree. In this case, a warning is produced above but we
# return the default dev version of '0'.
return '0'
elif not sha and returncode == 129:
# git returns 129 if a command option failed to parse; in
# particular this could happen in git versions older than 1.7.2
# where the --count option is not supported
# Also use --abbrev-commit and --abbrev=0 to display the minimum
# number of characters needed per-commit (rather than the full hash)
cmd = ['rev-list', '--abbrev-commit', '--abbrev=0', 'HEAD']
returncode, stdout, stderr = run_git(cmd)
# Fall back on the old method of getting all revisions and counting
# the lines
if returncode == 0:
return str(stdout.count(b'\n'))
else:
return ''
elif sha:
return _decode_stdio(stdout)[:40]
else:
return _decode_stdio(stdout).strip()
# This function is tested but it is only ever executed within a subprocess when
# creating a fake package, so it doesn't get picked up by coverage metrics.
def _get_repo_path(pathname, levels=None): # pragma: no cover
"""
Given a file or directory name, determine the root of the git repository
this path is under. If given, this won't look any higher than ``levels``
(that is, if ``levels=0`` then the given path must be the root of the git
repository and is returned if so.
Returns `None` if the given path could not be determined to belong to a git
repo.
"""
if os.path.isfile(pathname):
current_dir = os.path.abspath(os.path.dirname(pathname))
elif os.path.isdir(pathname):
current_dir = os.path.abspath(pathname)
else:
return None
current_level = 0
while levels is None or current_level <= levels:
if os.path.exists(os.path.join(current_dir, '.git')):
return current_dir
current_level += 1
if current_dir == os.path.dirname(current_dir):
break
current_dir = os.path.dirname(current_dir)
return None
# This module defines functions that can be used to check whether OpenMP is
# available and if so what flags to use. To use this, import the
# add_openmp_flags_if_available function in a setup_package.py file where you
# are defining your extensions:
#
# from astropy_helpers.openmp_helpers import add_openmp_flags_if_available
#
# then call it with a single extension as the only argument:
#
# add_openmp_flags_if_available(extension)
#
# this will add the OpenMP flags if available.
from __future__ import absolute_import, print_function
import os
import sys
import glob
import time
import datetime
import tempfile
import subprocess
from distutils import log
from distutils.ccompiler import new_compiler
from distutils.sysconfig import customize_compiler, get_config_var
from distutils.errors import CompileError, LinkError
from .setup_helpers import get_compiler_option
__all__ = ['add_openmp_flags_if_available']
try:
# Check if this has already been instantiated, only set the default once.
_ASTROPY_DISABLE_SETUP_WITH_OPENMP_
except NameError:
import builtins
# It hasn't, so do so.
builtins._ASTROPY_DISABLE_SETUP_WITH_OPENMP_ = False
CCODE = """
#include <omp.h>
#include <stdio.h>
int main(void) {
#pragma omp parallel
printf("nthreads=%d\\n", omp_get_num_threads());
return 0;
}
"""
def _get_flag_value_from_var(flag, var, delim=' '):
"""
Extract flags from an environment variable.
Parameters
----------
flag : str
The flag to extract, for example '-I' or '-L'
var : str
The environment variable to extract the flag from, e.g. CFLAGS or LDFLAGS.
delim : str, optional
The delimiter separating flags inside the environment variable
Examples
--------
Let's assume the LDFLAGS is set to '-L/usr/local/include -customflag'. This
function will then return the following:
>>> _get_flag_value_from_var('-L', 'LDFLAGS')
'/usr/local/include'
Notes
-----
Environment variables are first checked in ``os.environ[var]``, then in
``distutils.sysconfig.get_config_var(var)``.
This function is not supported on Windows.
"""
if sys.platform.startswith('win'):
return None
# Simple input validation
if not var or not flag:
return None
flag_length = len(flag)
if not flag_length:
return None
# Look for var in os.eviron then in get_config_var
if var in os.environ:
flags = os.environ[var]
else:
try:
flags = get_config_var(var)
except KeyError:
return None
# Extract flag from {var:value}
if flags:
for item in flags.split(delim):
if item.startswith(flag):
return item[flag_length:]
def get_openmp_flags():
"""
Utility for returning compiler and linker flags possibly needed for
OpenMP support.
Returns
-------
result : `{'compiler_flags':<flags>, 'linker_flags':<flags>}`
Notes
-----
The flags returned are not tested for validity, use
`check_openmp_support(openmp_flags=get_openmp_flags())` to do so.
"""
compile_flags = []
link_flags = []
if get_compiler_option() == 'msvc':
compile_flags.append('-openmp')
else:
include_path = _get_flag_value_from_var('-I', 'CFLAGS')
if include_path:
compile_flags.append('-I' + include_path)
lib_path = _get_flag_value_from_var('-L', 'LDFLAGS')
if lib_path:
link_flags.append('-L' + lib_path)
link_flags.append('-Wl,-rpath,' + lib_path)
compile_flags.append('-fopenmp')
link_flags.append('-fopenmp')
return {'compiler_flags': compile_flags, 'linker_flags': link_flags}
def check_openmp_support(openmp_flags=None):
"""
Check whether OpenMP test code can be compiled and run.
Parameters
----------
openmp_flags : dict, optional
This should be a dictionary with keys ``compiler_flags`` and
``linker_flags`` giving the compiliation and linking flags respectively.
These are passed as `extra_postargs` to `compile()` and
`link_executable()` respectively. If this is not set, the flags will
be automatically determined using environment variables.
Returns
-------
result : bool
`True` if the test passed, `False` otherwise.
"""
ccompiler = new_compiler()
customize_compiler(ccompiler)
if not openmp_flags:
# customize_compiler() extracts info from os.environ. If certain keys
# exist it uses these plus those from sysconfig.get_config_vars().
# If the key is missing in os.environ it is not extracted from
# sysconfig.get_config_var(). E.g. 'LDFLAGS' get left out, preventing
# clang from finding libomp.dylib because -L<path> is not passed to
# linker. Call get_openmp_flags() to get flags missed by
# customize_compiler().
openmp_flags = get_openmp_flags()
compile_flags = openmp_flags.get('compiler_flags')
link_flags = openmp_flags.get('linker_flags')
tmp_dir = tempfile.mkdtemp()
start_dir = os.path.abspath('.')
try:
os.chdir(tmp_dir)
# Write test program
with open('test_openmp.c', 'w') as f:
f.write(CCODE)
os.mkdir('objects')
# Compile, test program
ccompiler.compile(['test_openmp.c'], output_dir='objects',
extra_postargs=compile_flags)
# Link test program
objects = glob.glob(os.path.join('objects', '*' + ccompiler.obj_extension))
ccompiler.link_executable(objects, 'test_openmp',
extra_postargs=link_flags)
# Run test program
output = subprocess.check_output('./test_openmp')
output = output.decode(sys.stdout.encoding or 'utf-8').splitlines()
if 'nthreads=' in output[0]:
nthreads = int(output[0].strip().split('=')[1])
if len(output) == nthreads:
is_openmp_supported = True
else:
log.warn("Unexpected number of lines from output of test OpenMP "
"program (output was {0})".format(output))
is_openmp_supported = False
else:
log.warn("Unexpected output from test OpenMP "
"program (output was {0})".format(output))
is_openmp_supported = False
except (CompileError, LinkError, subprocess.CalledProcessError):
is_openmp_supported = False
finally:
os.chdir(start_dir)
return is_openmp_supported
def is_openmp_supported():
"""
Determine whether the build compiler has OpenMP support.
"""
log_threshold = log.set_threshold(log.FATAL)
ret = check_openmp_support()
log.set_threshold(log_threshold)
return ret
def add_openmp_flags_if_available(extension):
"""
Add OpenMP compilation flags, if supported (if not a warning will be
printed to the console and no flags will be added.)
Returns `True` if the flags were added, `False` otherwise.
"""
if _ASTROPY_DISABLE_SETUP_WITH_OPENMP_:
log.info("OpenMP support has been explicitly disabled.")
return False
openmp_flags = get_openmp_flags()
using_openmp = check_openmp_support(openmp_flags=openmp_flags)
if using_openmp:
compile_flags = openmp_flags.get('compiler_flags')
link_flags = openmp_flags.get('linker_flags')
log.info("Compiling Cython/C/C++ extension with OpenMP support")
extension.extra_compile_args.extend(compile_flags)
extension.extra_link_args.extend(link_flags)
else:
log.warn("Cannot compile Cython/C/C++ extension with OpenMP, reverting "
"to non-parallel code")
return using_openmp
_IS_OPENMP_ENABLED_SRC = """
# Autogenerated by {packagetitle}'s setup.py on {timestamp!s}
def is_openmp_enabled():
\"\"\"
Determine whether this package was built with OpenMP support.
\"\"\"
return {return_bool}
"""[1:]
def generate_openmp_enabled_py(packagename, srcdir='.', disable_openmp=None):
"""
Generate ``package.openmp_enabled.is_openmp_enabled``, which can then be used
to determine, post build, whether the package was built with or without
OpenMP support.
"""
if packagename.lower() == 'astropy':
packagetitle = 'Astropy'
else:
packagetitle = packagename
epoch = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
timestamp = datetime.datetime.utcfromtimestamp(epoch)
if disable_openmp is not None:
import builtins
builtins._ASTROPY_DISABLE_SETUP_WITH_OPENMP_ = disable_openmp
if _ASTROPY_DISABLE_SETUP_WITH_OPENMP_:
log.info("OpenMP support has been explicitly disabled.")
openmp_support = False if _ASTROPY_DISABLE_SETUP_WITH_OPENMP_ else is_openmp_supported()
src = _IS_OPENMP_ENABLED_SRC.format(packagetitle=packagetitle,
timestamp=timestamp,
return_bool=openmp_support)
package_srcdir = os.path.join(srcdir, *packagename.split('.'))
is_openmp_enabled_py = os.path.join(package_srcdir, 'openmp_enabled.py')
with open(is_openmp_enabled_py, 'w') as f:
f.write(src)
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
This module contains a number of utilities for use during
setup/build/packaging that are useful to astropy as a whole.
"""
from __future__ import absolute_import, print_function
import collections
import os
import re
import subprocess
import sys
import traceback
import warnings
from distutils import log
from distutils.errors import DistutilsOptionError, DistutilsModuleError
from distutils.core import Extension
from distutils.core import Command
from distutils.command.sdist import sdist as DistutilsSdist
from setuptools import find_packages as _find_packages
from .distutils_helpers import (add_command_option, get_compiler_option,
get_dummy_distribution, get_distutils_build_option,
get_distutils_build_or_install_option)
from .version_helpers import get_pkg_version_module
from .utils import (walk_skip_hidden, import_file, extends_doc,
resolve_name, AstropyDeprecationWarning)
from .commands.build_ext import generate_build_ext_command
from .commands.test import AstropyTest
# These imports are not used in this module, but are included for backwards
# compat with older versions of this module
from .utils import get_numpy_include_path, write_if_different # noqa
from .commands.build_ext import should_build_with_cython, get_compiler_version # noqa
_module_state = {'registered_commands': None,
'have_sphinx': False,
'package_cache': None,
'exclude_packages': set(),
'excludes_too_late': False}
try:
import sphinx # noqa
_module_state['have_sphinx'] = True
except ValueError as e:
# This can occur deep in the bowels of Sphinx's imports by way of docutils
# and an occurrence of this bug: http://bugs.python.org/issue18378
# In this case sphinx is effectively unusable
if 'unknown locale' in e.args[0]:
log.warn(
"Possible misconfiguration of one of the environment variables "
"LC_ALL, LC_CTYPES, LANG, or LANGUAGE. For an example of how to "
"configure your system's language environment on OSX see "
"http://blog.remibergsma.com/2012/07/10/"
"setting-locales-correctly-on-mac-osx-terminal-application/")
except ImportError:
pass
except SyntaxError:
# occurs if markupsafe is recent version, which doesn't support Python 3.2
pass
def adjust_compiler(package):
"""
This function detects broken compilers and switches to another. If
the environment variable CC is explicitly set, or a compiler is
specified on the commandline, no override is performed -- the purpose
here is to only override a default compiler.
The specific compilers with problems are:
* The default compiler in XCode-4.2, llvm-gcc-4.2,
segfaults when compiling wcslib.
The set of broken compilers can be updated by changing the
compiler_mapping variable. It is a list of 2-tuples where the
first in the pair is a regular expression matching the version
of the broken compiler, and the second is the compiler to change
to.
"""
warnings.warn(
'Direct use of the adjust_compiler function in setup.py is '
'deprecated and can be removed from your setup.py. This '
'functionality is now incorporated directly into the build_ext '
'command.', AstropyDeprecationWarning)
def get_debug_option(packagename):
""" Determines if the build is in debug mode.
Returns
-------
debug : bool
True if the current build was started with the debug option, False
otherwise.
"""
try:
current_debug = get_pkg_version_module(packagename,
fromlist=['debug'])[0]
except (ImportError, AttributeError):
current_debug = None
# Only modify the debug flag if one of the build commands was explicitly
# run (i.e. not as a sub-command of something else)
dist = get_dummy_distribution()
if any(cmd in dist.commands for cmd in ['build', 'build_ext']):
debug = bool(get_distutils_build_option('debug'))
else:
debug = bool(current_debug)
if current_debug is not None and current_debug != debug:
build_ext_cmd = dist.get_command_class('build_ext')
build_ext_cmd.force_rebuild = True
return debug
def add_exclude_packages(excludes):
if _module_state['excludes_too_late']:
raise RuntimeError(
"add_package_excludes must be called before all other setup helper "
"functions in order to properly handle excluded packages")
_module_state['exclude_packages'].update(set(excludes))
def register_commands(package, version, release, srcdir='.'):
if _module_state['registered_commands'] is not None:
return _module_state['registered_commands']
if _module_state['have_sphinx']:
try:
from .commands.build_sphinx import (AstropyBuildSphinx,
AstropyBuildDocs)
except ImportError:
AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx
else:
AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx
_module_state['registered_commands'] = registered_commands = {
'test': generate_test_command(package),
# Use distutils' sdist because it respects package_data.
# setuptools/distributes sdist requires duplication of information in
# MANIFEST.in
'sdist': DistutilsSdist,
# The exact form of the build_ext command depends on whether or not
# we're building a release version
'build_ext': generate_build_ext_command(package, release),
'build_sphinx': AstropyBuildSphinx,
'build_docs': AstropyBuildDocs
}
# Need to override the __name__ here so that the commandline options are
# presented as being related to the "build" command, for example; normally
# this wouldn't be necessary since commands also have a command_name
# attribute, but there is a bug in distutils' help display code that it
# uses __name__ instead of command_name. Yay distutils!
for name, cls in registered_commands.items():
cls.__name__ = name
# Add a few custom options; more of these can be added by specific packages
# later
for option in [
('use-system-libraries',
"Use system libraries whenever possible", True)]:
add_command_option('build', *option)
add_command_option('install', *option)
add_command_hooks(registered_commands, srcdir=srcdir)
return registered_commands
def add_command_hooks(commands, srcdir='.'):
"""
Look through setup_package.py modules for functions with names like
``pre_<command_name>_hook`` and ``post_<command_name>_hook`` where
``<command_name>`` is the name of a ``setup.py`` command (e.g. build_ext).
If either hook is present this adds a wrapped version of that command to
the passed in ``commands`` `dict`. ``commands`` may be pre-populated with
other custom distutils command classes that should be wrapped if there are
hooks for them (e.g. `AstropyBuildPy`).
"""
hook_re = re.compile(r'^(pre|post)_(.+)_hook$')
# Distutils commands have a method of the same name, but it is not a
# *classmethod* (which probably didn't exist when distutils was first
# written)
def get_command_name(cmdcls):
if hasattr(cmdcls, 'command_name'):
return cmdcls.command_name
else:
return cmdcls.__name__
packages = find_packages(srcdir)
dist = get_dummy_distribution()
hooks = collections.defaultdict(dict)
for setuppkg in iter_setup_packages(srcdir, packages):
for name, obj in vars(setuppkg).items():
match = hook_re.match(name)
if not match:
continue
hook_type = match.group(1)
cmd_name = match.group(2)
if hook_type not in hooks[cmd_name]:
hooks[cmd_name][hook_type] = []
hooks[cmd_name][hook_type].append((setuppkg.__name__, obj))
for cmd_name, cmd_hooks in hooks.items():
commands[cmd_name] = generate_hooked_command(
cmd_name, dist.get_command_class(cmd_name), cmd_hooks)
def generate_hooked_command(cmd_name, cmd_cls, hooks):
"""
Returns a generated subclass of ``cmd_cls`` that runs the pre- and
post-command hooks for that command before and after the ``cmd_cls.run``
method.
"""
def run(self, orig_run=cmd_cls.run):
self.run_command_hooks('pre_hooks')
orig_run(self)
self.run_command_hooks('post_hooks')
return type(cmd_name, (cmd_cls, object),
{'run': run, 'run_command_hooks': run_command_hooks,
'pre_hooks': hooks.get('pre', []),
'post_hooks': hooks.get('post', [])})
def run_command_hooks(cmd_obj, hook_kind):
"""Run hooks registered for that command and phase.
*cmd_obj* is a finalized command object; *hook_kind* is either
'pre_hook' or 'post_hook'.
"""
hooks = getattr(cmd_obj, hook_kind, None)
if not hooks:
return
for modname, hook in hooks:
if isinstance(hook, str):
try:
hook_obj = resolve_name(hook)
except ImportError as exc:
raise DistutilsModuleError(
'cannot find hook {0}: {1}'.format(hook, exc))
else:
hook_obj = hook
if not callable(hook_obj):
raise DistutilsOptionError('hook {0!r} is not callable' % hook)
log.info('running {0} from {1} for {2} command'.format(
hook_kind.rstrip('s'), modname, cmd_obj.get_command_name()))
try:
hook_obj(cmd_obj)
except Exception:
log.error('{0} command hook {1} raised an exception: %s\n'.format(
hook_obj.__name__, cmd_obj.get_command_name()))
log.error(traceback.format_exc())
sys.exit(1)
def generate_test_command(package_name):
"""
Creates a custom 'test' command for the given package which sets the
command's ``package_name`` class attribute to the name of the package being
tested.
"""
return type(package_name.title() + 'Test', (AstropyTest,),
{'package_name': package_name})
def update_package_files(srcdir, extensions, package_data, packagenames,
package_dirs):
"""
This function is deprecated and maintained for backward compatibility
with affiliated packages. Affiliated packages should update their
setup.py to use `get_package_info` instead.
"""
info = get_package_info(srcdir)
extensions.extend(info['ext_modules'])
package_data.update(info['package_data'])
packagenames = list(set(packagenames + info['packages']))
package_dirs.update(info['package_dir'])
def get_package_info(srcdir='.', exclude=()):
"""
Collates all of the information for building all subpackages
and returns a dictionary of keyword arguments that can
be passed directly to `distutils.setup`.
The purpose of this function is to allow subpackages to update the
arguments to the package's ``setup()`` function in its setup.py
script, rather than having to specify all extensions/package data
directly in the ``setup.py``. See Astropy's own
``setup.py`` for example usage and the Astropy development docs
for more details.
This function obtains that information by iterating through all
packages in ``srcdir`` and locating a ``setup_package.py`` module.
This module can contain the following functions:
``get_extensions()``, ``get_package_data()``,
``get_build_options()``, and ``get_external_libraries()``.
Each of those functions take no arguments.
- ``get_extensions`` returns a list of
`distutils.extension.Extension` objects.
- ``get_package_data()`` returns a dict formatted as required by
the ``package_data`` argument to ``setup()``.
- ``get_build_options()`` returns a list of tuples describing the
extra build options to add.
- ``get_external_libraries()`` returns
a list of libraries that can optionally be built using external
dependencies.
- ``get_entry_points()`` returns a dict formatted as required by
the ``entry_points`` argument to ``setup()``.
"""
ext_modules = []
packages = []
package_data = {}
package_dir = {}
if exclude:
warnings.warn(
"Use of the exclude parameter is no longer supported since it does "
"not work as expected. Use add_exclude_packages instead. Note that "
"it must be called prior to any other calls from setup helpers.",
AstropyDeprecationWarning)
# Use the find_packages tool to locate all packages and modules
packages = find_packages(srcdir, exclude=exclude)
# Update package_dir if the package lies in a subdirectory
if srcdir != '.':
package_dir[''] = srcdir
# For each of the setup_package.py modules, extract any
# information that is needed to install them. The build options
# are extracted first, so that their values will be available in
# subsequent calls to `get_extensions`, etc.
for setuppkg in iter_setup_packages(srcdir, packages):
if hasattr(setuppkg, 'get_build_options'):
options = setuppkg.get_build_options()
for option in options:
add_command_option('build', *option)
if hasattr(setuppkg, 'get_external_libraries'):
libraries = setuppkg.get_external_libraries()
for library in libraries:
add_external_library(library)
for setuppkg in iter_setup_packages(srcdir, packages):
# get_extensions must include any Cython extensions by their .pyx
# filename.
if hasattr(setuppkg, 'get_extensions'):
ext_modules.extend(setuppkg.get_extensions())
if hasattr(setuppkg, 'get_package_data'):
package_data.update(setuppkg.get_package_data())
# Locate any .pyx files not already specified, and add their extensions in.
# The default include dirs include numpy to facilitate numerical work.
ext_modules.extend(get_cython_extensions(srcdir, packages, ext_modules,
['numpy']))
# Now remove extensions that have the special name 'skip_cython', as they
# exist Only to indicate that the cython extensions shouldn't be built
for i, ext in reversed(list(enumerate(ext_modules))):
if ext.name == 'skip_cython':
del ext_modules[i]
# On Microsoft compilers, we need to pass the '/MANIFEST'
# commandline argument. This was the default on MSVC 9.0, but is
# now required on MSVC 10.0, but it doesn't seem to hurt to add
# it unconditionally.
if get_compiler_option() == 'msvc':
for ext in ext_modules:
ext.extra_link_args.append('/MANIFEST')
return {
'ext_modules': ext_modules,
'packages': packages,
'package_dir': package_dir,
'package_data': package_data,
}
def iter_setup_packages(srcdir, packages):
""" A generator that finds and imports all of the ``setup_package.py``
modules in the source packages.
Returns
-------
modgen : generator
A generator that yields (modname, mod), where `mod` is the module and
`modname` is the module name for the ``setup_package.py`` modules.
"""
for packagename in packages:
package_parts = packagename.split('.')
package_path = os.path.join(srcdir, *package_parts)
setup_package = os.path.relpath(
os.path.join(package_path, 'setup_package.py'))
if os.path.isfile(setup_package):
module = import_file(setup_package,
name=packagename + '.setup_package')
yield module
def iter_pyx_files(package_dir, package_name):
"""
A generator that yields Cython source files (ending in '.pyx') in the
source packages.
Returns
-------
pyxgen : generator
A generator that yields (extmod, fullfn) where `extmod` is the
full name of the module that the .pyx file would live in based
on the source directory structure, and `fullfn` is the path to
the .pyx file.
"""
for dirpath, dirnames, filenames in walk_skip_hidden(package_dir):
for fn in filenames:
if fn.endswith('.pyx'):
fullfn = os.path.relpath(os.path.join(dirpath, fn))
# Package must match file name
extmod = '.'.join([package_name, fn[:-4]])
yield (extmod, fullfn)
break # Don't recurse into subdirectories
def get_cython_extensions(srcdir, packages, prevextensions=tuple(),
extincludedirs=None):
"""
Looks for Cython files and generates Extensions if needed.
Parameters
----------
srcdir : str
Path to the root of the source directory to search.
prevextensions : list of `~distutils.core.Extension` objects
The extensions that are already defined. Any .pyx files already here
will be ignored.
extincludedirs : list of str or None
Directories to include as the `include_dirs` argument to the generated
`~distutils.core.Extension` objects.
Returns
-------
exts : list of `~distutils.core.Extension` objects
The new extensions that are needed to compile all .pyx files (does not
include any already in `prevextensions`).
"""
# Vanilla setuptools and old versions of distribute include Cython files
# as .c files in the sources, not .pyx, so we cannot simply look for
# existing .pyx sources in the previous sources, but we should also check
# for .c files with the same remaining filename. So we look for .pyx and
# .c files, and we strip the extension.
prevsourcepaths = []
ext_modules = []
for ext in prevextensions:
for s in ext.sources:
if s.endswith(('.pyx', '.c', '.cpp')):
sourcepath = os.path.realpath(os.path.splitext(s)[0])
prevsourcepaths.append(sourcepath)
for package_name in packages:
package_parts = package_name.split('.')
package_path = os.path.join(srcdir, *package_parts)
for extmod, pyxfn in iter_pyx_files(package_path, package_name):
sourcepath = os.path.realpath(os.path.splitext(pyxfn)[0])
if sourcepath not in prevsourcepaths:
ext_modules.append(Extension(extmod, [pyxfn],
include_dirs=extincludedirs))
return ext_modules
class DistutilsExtensionArgs(collections.defaultdict):
"""
A special dictionary whose default values are the empty list.
This is useful for building up a set of arguments for
`distutils.Extension` without worrying whether the entry is
already present.
"""
def __init__(self, *args, **kwargs):
def default_factory():
return []
super(DistutilsExtensionArgs, self).__init__(
default_factory, *args, **kwargs)
def update(self, other):
for key, val in other.items():
self[key].extend(val)
def pkg_config(packages, default_libraries, executable='pkg-config'):
"""
Uses pkg-config to update a set of distutils Extension arguments
to include the flags necessary to link against the given packages.
If the pkg-config lookup fails, default_libraries is applied to
libraries.
Parameters
----------
packages : list of str
A list of pkg-config packages to look up.
default_libraries : list of str
A list of library names to use if the pkg-config lookup fails.
Returns
-------
config : dict
A dictionary containing keyword arguments to
`distutils.Extension`. These entries include:
- ``include_dirs``: A list of include directories
- ``library_dirs``: A list of library directories
- ``libraries``: A list of libraries
- ``define_macros``: A list of macro defines
- ``undef_macros``: A list of macros to undefine
- ``extra_compile_args``: A list of extra arguments to pass to
the compiler
"""
flag_map = {'-I': 'include_dirs', '-L': 'library_dirs', '-l': 'libraries',
'-D': 'define_macros', '-U': 'undef_macros'}
command = "{0} --libs --cflags {1}".format(executable, ' '.join(packages)),
result = DistutilsExtensionArgs()
try:
pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
output = pipe.communicate()[0].strip()
except subprocess.CalledProcessError as e:
lines = [
("{0} failed. This may cause the build to fail below."
.format(executable)),
" command: {0}".format(e.cmd),
" returncode: {0}".format(e.returncode),
" output: {0}".format(e.output)
]
log.warn('\n'.join(lines))
result['libraries'].extend(default_libraries)
else:
if pipe.returncode != 0:
lines = [
"pkg-config could not lookup up package(s) {0}.".format(
", ".join(packages)),
"This may cause the build to fail below."
]
log.warn('\n'.join(lines))
result['libraries'].extend(default_libraries)
else:
for token in output.split():
# It's not clear what encoding the output of
# pkg-config will come to us in. It will probably be
# some combination of pure ASCII (for the compiler
# flags) and the filesystem encoding (for any argument
# that includes directories or filenames), but this is
# just conjecture, as the pkg-config documentation
# doesn't seem to address it.
arg = token[:2].decode('ascii')
value = token[2:].decode(sys.getfilesystemencoding())
if arg in flag_map:
if arg == '-D':
value = tuple(value.split('=', 1))
result[flag_map[arg]].append(value)
else:
result['extra_compile_args'].append(value)
return result
def add_external_library(library):
"""
Add a build option for selecting the internal or system copy of a library.
Parameters
----------
library : str
The name of the library. If the library is `foo`, the build
option will be called `--use-system-foo`.
"""
for command in ['build', 'build_ext', 'install']:
add_command_option(command, str('use-system-' + library),
'Use the system {0} library'.format(library),
is_bool=True)
def use_system_library(library):
"""
Returns `True` if the build configuration indicates that the given
library should use the system copy of the library rather than the
internal one.
For the given library `foo`, this will be `True` if
`--use-system-foo` or `--use-system-libraries` was provided at the
commandline or in `setup.cfg`.
Parameters
----------
library : str
The name of the library
Returns
-------
use_system : bool
`True` if the build should use the system copy of the library.
"""
return (
get_distutils_build_or_install_option('use_system_{0}'.format(library)) or
get_distutils_build_or_install_option('use_system_libraries'))
@extends_doc(_find_packages)
def find_packages(where='.', exclude=(), invalidate_cache=False):
"""
This version of ``find_packages`` caches previous results to speed up
subsequent calls. Use ``invalide_cache=True`` to ignore cached results
from previous ``find_packages`` calls, and repeat the package search.
"""
if exclude:
warnings.warn(
"Use of the exclude parameter is no longer supported since it does "
"not work as expected. Use add_exclude_packages instead. Note that "
"it must be called prior to any other calls from setup helpers.",
AstropyDeprecationWarning)
# Calling add_exclude_packages after this point will have no effect
_module_state['excludes_too_late'] = True
if not invalidate_cache and _module_state['package_cache'] is not None:
return _module_state['package_cache']
packages = _find_packages(
where=where, exclude=list(_module_state['exclude_packages']))
_module_state['package_cache'] = packages
return packages
class FakeBuildSphinx(Command):
"""
A dummy build_sphinx command that is called if Sphinx is not
installed and displays a relevant error message
"""
# user options inherited from sphinx.setup_command.BuildDoc
user_options = [
('fresh-env', 'E', ''),
('all-files', 'a', ''),
('source-dir=', 's', ''),
('build-dir=', None, ''),
('config-dir=', 'c', ''),
('builder=', 'b', ''),
('project=', None, ''),
('version=', None, ''),
('release=', None, ''),
('today=', None, ''),
('link-index', 'i', '')]
# user options appended in astropy.setup_helpers.AstropyBuildSphinx
user_options.append(('warnings-returncode', 'w', ''))
user_options.append(('clean-docs', 'l', ''))
user_options.append(('no-intersphinx', 'n', ''))
user_options.append(('open-docs-in-browser', 'o', ''))
def initialize_options(self):
try:
raise RuntimeError("Sphinx and its dependencies must be installed "
"for build_docs.")
except:
log.error('error: Sphinx and its dependencies must be installed '
'for build_docs.')
sys.exit(1)
import warnings
from sphinx_astropy.conf import *
warnings.warn("Note that astropy_helpers.sphinx.conf is deprecated - use sphinx_astropy.conf instead")
import os
import subprocess as sp
import sys
import pytest
try:
from coverage import CoverageData
except ImportError:
HAS_COVERAGE = False
else:
HAS_COVERAGE = True
from ..conftest import SUBPROCESS_COVERAGE
PACKAGE_DIR = os.path.dirname(__file__)
def run_cmd(cmd, args, path=None, raise_error=True):
"""
Runs a shell command with the given argument list. Changes directory to
``path`` if given, otherwise runs the command in the current directory.
Returns a 3-tuple of (stdout, stderr, exit code)
If ``raise_error=True`` raise an exception on non-zero exit codes.
"""
if path is not None:
# Transparently support py.path objects
path = str(path)
p = sp.Popen([cmd] + list(args), stdout=sp.PIPE, stderr=sp.PIPE,
cwd=path)
streams = tuple(s.decode('latin1').strip() for s in p.communicate())
return_code = p.returncode
if raise_error and return_code != 0:
raise RuntimeError(
"The command `{0}` with args {1!r} exited with code {2}.\n"
"Stdout:\n\n{3}\n\nStderr:\n\n{4}".format(
cmd, list(args), return_code, streams[0], streams[1]))
return streams + (return_code,)
def run_setup(setup_script, args):
# This used to call setuptools.sandbox's run_setup, but due to issues with
# this and Cython (which caused segmentation faults), we now use subprocess.
setup_script = os.path.abspath(setup_script)
path = os.path.dirname(setup_script)
setup_script = os.path.basename(setup_script)
if HAS_COVERAGE:
# In this case, we run the command using the coverage command and we
# then collect the coverage data into a SUBPROCESS_COVERAGE list which
# is set up at the start of the testing process and is then combined
# into a single .coverage file at the end of the testing process.
p = sp.Popen(['coverage', 'run', setup_script] + list(args), cwd=path,
stdout=sp.PIPE, stderr=sp.PIPE)
stdout, stderr = p.communicate()
cdata = CoverageData()
cdata.read_file(os.path.join(path, '.coverage'))
SUBPROCESS_COVERAGE.append(cdata)
else:
# Otherwise we just run the tests with Python
p = sp.Popen([sys.executable, setup_script] + list(args), cwd=path,
stdout=sp.PIPE, stderr=sp.PIPE)
stdout, stderr = p.communicate()
sys.stdout.write(stdout.decode('utf-8'))
sys.stderr.write(stderr.decode('utf-8'))
if p.returncode != 0:
raise SystemExit(p.returncode)
@pytest.fixture(scope='function', autouse=True)
def reset_setup_helpers(request):
"""
Saves and restores the global state of the astropy_helpers.setup_helpers
module between tests.
"""
mod = __import__('astropy_helpers.setup_helpers', fromlist=[''])
old_state = mod._module_state.copy()
def finalizer(old_state=old_state):
mod = sys.modules.get('astropy_helpers.setup_helpers')
if mod is not None:
mod._module_state.update(old_state)
request.addfinalizer(finalizer)
@pytest.fixture(scope='function', autouse=True)
def reset_distutils_log():
"""
This is a setup/teardown fixture that ensures the log-level of the
distutils log is always set to a default of WARN, since different
settings could affect tests that check the contents of stdout.
"""
from distutils import log
log.set_threshold(log.WARN)
TEST_PACKAGE_SETUP_PY = """\
#!/usr/bin/env python
from setuptools import setup
NAME = 'astropy-helpers-test'
VERSION = {version!r}
setup(name=NAME, version=VERSION,
packages=['_astropy_helpers_test_'],
zip_safe=False)
"""
def create_testpackage(tmpdir, version='0.1'):
source = tmpdir.mkdir('testpkg')
with source.as_cwd():
source.mkdir('_astropy_helpers_test_')
init = source.join('_astropy_helpers_test_', '__init__.py')
init.write('__version__ = {0!r}'.format(version))
setup_py = TEST_PACKAGE_SETUP_PY.format(version=version)
source.join('setup.py').write(setup_py)
# Make the new test package into a git repo
run_cmd('git', ['init'])
run_cmd('git', ['add', '--all'])
run_cmd('git', ['commit', '-m', 'test package'])
return source
@pytest.fixture
def testpackage(tmpdir, version='0.1'):
"""
This fixture creates a simplified package called _astropy_helpers_test_
used primarily for testing ah_boostrap, but without using the
astropy_helpers package directly and getting it confused with the
astropy_helpers package already under test.
"""
return create_testpackage(tmpdir, version=version)
def cleanup_import(package_name):
"""Remove all references to package_name from sys.modules"""
for k in list(sys.modules):
if not isinstance(k, str):
# Some things will actually do this =_=
continue
elif k.startswith('astropy_helpers.tests'):
# Don't delete imported test modules or else the tests will break,
# badly
continue
if k == package_name or k.startswith(package_name + '.'):
del sys.modules[k]
# -*- coding: utf-8 -*-
import glob
import os
import json
import textwrap
from distutils.version import LooseVersion
import setuptools
import pytest
from . import reset_setup_helpers, reset_distutils_log # noqa
from . import run_cmd, run_setup, testpackage, create_testpackage
from ..utils import silence
TEST_SETUP_PY = """\
#!/usr/bin/env python
from __future__ import print_function
import os
import sys
# This import is not the real run of ah_bootstrap for the purposes of the test,
# so we need to preserve the command-line arguments otherwise these get eaten
# up by this import
args = sys.argv[:]
import ah_bootstrap
sys.argv = args
{extra}
# reset the name of the package installed by ah_boostrap to
# _astropy_helpers_test_--this will prevent any confusion by pkg_resources with
# any already installed packages named astropy_helpers
# We also disable auto-upgrade by default
ah_bootstrap.DIST_NAME = 'astropy-helpers-test'
ah_bootstrap.PACKAGE_NAME = '_astropy_helpers_test_'
ah_bootstrap.AUTO_UPGRADE = False
ah_bootstrap.DOWNLOAD_IF_NEEDED = False
try:
ah_bootstrap.BOOTSTRAPPER = ah_bootstrap._Bootstrapper.main()
ah_bootstrap.use_astropy_helpers({args})
finally:
ah_bootstrap.DIST_NAME = 'astropy-helpers'
ah_bootstrap.PACKAGE_NAME = 'astropy_helpers'
ah_bootstrap.AUTO_UPGRADE = True
ah_bootstrap.DOWNLOAD_IF_NEEDED = True
# Kind of a hacky way to do this, but this assertion is specifically
# for test_check_submodule_no_git
# TODO: Rework the tests in this module so that it's easier to test specific
# behaviors of ah_bootstrap for each test
assert '--no-git' not in sys.argv
import _astropy_helpers_test_
filename = os.path.abspath(_astropy_helpers_test_.__file__)
filename = filename.replace('.pyc', '.py') # More consistent this way
# We print out variables that are needed in tests below in JSON
import json
data = {{}}
data['filename'] = filename
data['ah_bootstrap.BOOTSTRAPPER.use_git'] = ah_bootstrap.BOOTSTRAPPER.use_git
print(json.dumps(data))
"""
AH_BOOTSTRAP_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'ah_bootstrap.py')
with open(AH_BOOTSTRAP_FILE) as f:
AH_BOOTSTRAP = f.read()
# The behavior checked in some of the tests depends on the version of
# setuptools
try:
# We need to use LooseVersion here instead of StrictVersion since developer
# versions of setuptools ('35.0.2.post20170530') don't satisfy the
# StrictVersion criteria even though they satisfy PEP440
SETUPTOOLS_VERSION = LooseVersion(setuptools.__version__).version
except:
# Broken setuptools? ¯\_(ツ)_/¯
SETUPTOOLS_VERSION = (0, 0, 0)
def test_bootstrap_from_submodule(tmpdir, testpackage, capsys):
"""
Tests importing _astropy_helpers_test_ from a submodule in a git
repository. This tests actually performing a fresh clone of the repository
without the submodule initialized, and that importing astropy_helpers in
that context works transparently after calling
`ah_boostrap.use_astropy_helpers`.
"""
orig_repo = tmpdir.mkdir('orig')
with orig_repo.as_cwd():
run_cmd('git', ['init'])
orig_repo.join('ah_bootstrap.py').write(AH_BOOTSTRAP)
run_cmd('git', ['add', 'ah_bootstrap.py'])
# Write a test setup.py that uses ah_bootstrap; it also ensures that
# any previous reference to astropy_helpers is first wiped from
# sys.modules
orig_repo.join('setup.py').write(TEST_SETUP_PY.format(args='', extra=''))
run_cmd('git', ['add', 'setup.py'])
# Add our own clone of the astropy_helpers repo as a submodule named
# astropy_helpers
run_cmd('git', ['submodule', 'add', str(testpackage),
'_astropy_helpers_test_'])
run_cmd('git', ['commit', '-m', 'test repository'])
os.chdir(str(tmpdir))
# Creates a clone of our test repo in the directory 'clone'
run_cmd('git', ['clone', 'orig', 'clone'])
os.chdir('clone')
run_setup('setup.py', [])
stdout, stderr = capsys.readouterr()
path = json.loads(stdout.strip())['filename']
# Ensure that the astropy_helpers used by the setup.py is the one that
# was imported from git submodule
a = os.path.normcase(path)
b = os.path.normcase(str(tmpdir.join('clone', '_astropy_helpers_test_',
'_astropy_helpers_test_',
'__init__.py')))
assert a == b
def test_bootstrap_from_submodule_no_locale(tmpdir, testpackage, capsys,
monkeypatch):
"""
Regression test for https://github.com/astropy/astropy/issues/2749
Runs test_bootstrap_from_submodule but with missing locale/language
settings.
"""
for varname in ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'):
monkeypatch.delenv(varname, raising=False)
test_bootstrap_from_submodule(tmpdir, testpackage, capsys)
def test_bootstrap_from_submodule_bad_locale(tmpdir, testpackage, capsys,
monkeypatch):
"""
Additional regression test for
https://github.com/astropy/astropy/issues/2749
"""
for varname in ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'):
monkeypatch.delenv(varname, raising=False)
# Test also with bad LC_CTYPE a la http://bugs.python.org/issue18378
monkeypatch.setenv('LC_CTYPE', 'UTF-8')
test_bootstrap_from_submodule(tmpdir, testpackage, capsys)
UPDATE_ERROR_PATCH = """
class UpgradeError(Exception):
pass
def _do_upgrade(*args, **kwargs):
raise UpgradeError()
ah_bootstrap._Bootstrapper._do_upgrade = _do_upgrade
"""
def test_check_submodule_no_git(capsys, tmpdir, testpackage):
"""
Tests that when importing astropy_helpers from a submodule, it is still
recognized as a submodule even when using the --no-git option.
In particular this ensures that the auto-upgrade feature is not activated.
"""
orig_repo = tmpdir.mkdir('orig')
with orig_repo.as_cwd():
orig_repo.join('ah_bootstrap.py').write(AH_BOOTSTRAP)
run_cmd('git', ['init'])
# Write a test setup.py that uses ah_bootstrap; it also ensures that
# any previous reference to astropy_helpers is first wiped from
# sys.modules
args = 'auto_upgrade=True'
orig_repo.join('setup.py').write(TEST_SETUP_PY.format(args=args, extra=UPDATE_ERROR_PATCH))
run_cmd('git', ['add', 'setup.py'])
# Add our own clone of the astropy_helpers repo as a submodule named
# astropy_helpers
run_cmd('git', ['submodule', 'add', str(testpackage),
'_astropy_helpers_test_'])
run_cmd('git', ['commit', '-m', 'test repository'])
run_setup('setup.py', ['--no-git'])
stdout, stderr = capsys.readouterr()
use_git = bool(json.loads(stdout.strip())['ah_bootstrap.BOOTSTRAPPER.use_git'])
if 'UpgradeError' in stderr:
pytest.fail('Attempted to run auto-upgrade despite importing '
'_astropy_helpers_test_ from a git submodule')
# Ensure that the no-git option was in fact set
assert not use_git
def test_bootstrap_from_directory(tmpdir, testpackage, capsys):
"""
Tests simply bundling a copy of the astropy_helpers source code in its
entirety bundled directly in the source package and not in an archive.
"""
source = tmpdir.mkdir('source')
testpackage.copy(source.join('_astropy_helpers_test_'))
with source.as_cwd():
source.join('ah_bootstrap.py').write(AH_BOOTSTRAP)
source.join('setup.py').write(TEST_SETUP_PY.format(args='', extra=''))
run_setup('setup.py', [])
stdout, stderr = capsys.readouterr()
path = json.loads(stdout.strip())['filename']
# Ensure that the astropy_helpers used by the setup.py is the one that
# was imported from git submodule
a = os.path.normcase(path)
b = os.path.normcase(str(source.join('_astropy_helpers_test_',
'_astropy_helpers_test_',
'__init__.py')))
assert a == b
def test_bootstrap_from_archive(tmpdir, testpackage, capsys):
"""
Tests importing _astropy_helpers_test_ from a .tar.gz source archive
shipped alongside the package that uses it.
"""
orig_repo = tmpdir.mkdir('orig')
# Make a source distribution of the test package
with silence():
run_setup(str(testpackage.join('setup.py')),
['sdist', '--dist-dir=dist', '--formats=gztar'])
dist_dir = testpackage.join('dist')
for dist_file in dist_dir.visit('*.tar.gz'):
dist_file.copy(orig_repo)
with orig_repo.as_cwd():
orig_repo.join('ah_bootstrap.py').write(AH_BOOTSTRAP)
# Write a test setup.py that uses ah_bootstrap; it also ensures that
# any previous reference to astropy_helpers is first wiped from
# sys.modules
args = 'path={0!r}'.format(os.path.basename(str(dist_file)))
orig_repo.join('setup.py').write(TEST_SETUP_PY.format(args=args, extra=''))
run_setup('setup.py', [])
stdout, stderr = capsys.readouterr()
path = json.loads(stdout.strip())['filename']
# Installation from the .tar.gz should have resulted in a .egg
# directory that the _astropy_helpers_test_ package was imported from
eggs = _get_local_eggs()
assert eggs
egg = orig_repo.join(eggs[0])
assert os.path.isdir(str(egg))
a = os.path.normcase(path)
b = os.path.normcase(str(egg.join('_astropy_helpers_test_',
'__init__.py')))
assert a == b
def test_download_if_needed(tmpdir, testpackage, capsys):
"""
Tests the case where astropy_helpers was not actually included in a
package, or is otherwise missing, and we need to "download" it.
This does not test actually downloading from the internet--this is normally
done through setuptools' easy_install command which can also install from a
source archive. From the point of view of ah_boostrap the two actions are
equivalent, so we can just as easily simulate this by providing a setup.cfg
giving the path to a source archive to "download" (as though it were a
URL).
"""
source = tmpdir.mkdir('source')
# Ensure ah_bootstrap is imported from the local directory
import ah_bootstrap # noqa
# Make a source distribution of the test package
with silence():
run_setup(str(testpackage.join('setup.py')),
['sdist', '--dist-dir=dist', '--formats=gztar'])
dist_dir = testpackage.join('dist')
with source.as_cwd():
source.join('ah_bootstrap.py').write(AH_BOOTSTRAP)
source.join('setup.py').write(TEST_SETUP_PY.format(
args='download_if_needed=True', extra=''))
source.join('setup.cfg').write(textwrap.dedent("""\
[easy_install]
find_links = {find_links}
""".format(find_links=str(dist_dir))))
run_setup('setup.py', [])
stdout, stderr = capsys.readouterr()
path = json.loads(stdout.strip())['filename']
# easy_install should have worked by 'installing' astropy_helpers as a
# .egg in the current directory
eggs = _get_local_eggs()
assert eggs
egg = source.join(eggs[0])
assert os.path.isdir(str(egg))
a = os.path.normcase(path)
b = os.path.normcase(str(egg.join('_astropy_helpers_test_',
'__init__.py')))
assert a == b
EXTRA_PACKAGE_INDEX = """
from setuptools.package_index import PackageIndex
class FakePackageIndex(PackageIndex):
def __init__(self, *args, **kwargs):
PackageIndex.__init__(self, *args, **kwargs)
self.to_scan = {dists}
def find_packages(self, requirement):
# no-op
pass
ah_bootstrap.PackageIndex = FakePackageIndex
"""
def test_upgrade(tmpdir, capsys):
orig_dir = create_testpackage(tmpdir.mkdir('orig'))
# Make a test package that uses _astropy_helpers_test_
source = tmpdir.mkdir('source')
dist_dir = source.mkdir('dists')
orig_dir.copy(source.join('_astropy_helpers_test_'))
with source.as_cwd():
source.join('ah_bootstrap.py').write(AH_BOOTSTRAP)
setup_py = TEST_SETUP_PY.format(args='auto_upgrade=True', extra='')
source.join('setup.py').write(setup_py)
# This will be used to later to fake downloading the upgrade package
source.join('setup.cfg').write(textwrap.dedent("""\
[easy_install]
find_links = {find_links}
""".format(find_links=str(dist_dir))))
# Make additional "upgrade" versions of the _astropy_helpers_test_
# package--one of them is version 0.2 and the other is version 0.1.1. The
# auto-upgrade should ignore version 0.2 but use version 0.1.1.
upgrade_dir_1 = create_testpackage(tmpdir.mkdir('upgrade_1'), version='0.2')
upgrade_dir_2 = create_testpackage(tmpdir.mkdir('upgrade_2'), version='0.1.1')
dists = []
# For each upgrade package go ahead and build a source distribution of it
# and copy that source distribution to a dist directory we'll use later to
# simulate a 'download'
for upgrade_dir in [upgrade_dir_1, upgrade_dir_2]:
with silence():
run_setup(str(upgrade_dir.join('setup.py')),
['sdist', '--dist-dir=dist', '--formats=gztar'])
dists.append(str(upgrade_dir.join('dist')))
for dist_file in upgrade_dir.visit('*.tar.gz'):
dist_file.copy(source.join('dists'))
with source.as_cwd():
setup_py = TEST_SETUP_PY.format(args='auto_upgrade=True',
extra=EXTRA_PACKAGE_INDEX.format(dists=dists))
source.join('setup.py').write(setup_py)
# Now run the source setup.py; this test is similar to
# test_download_if_needed, but we explicitly check that the correct
# *version* of _astropy_helpers_test_ was used
run_setup('setup.py', [])
stdout, stderr = capsys.readouterr()
path = json.loads(stdout.strip())['filename']
eggs = _get_local_eggs()
assert eggs
egg = source.join(eggs[0])
assert os.path.isdir(str(egg))
a = os.path.normcase(path)
b = os.path.normcase(str(egg.join('_astropy_helpers_test_',
'__init__.py')))
assert a == b
assert 'astropy_helpers_test-0.1.1-' in str(egg)
def _get_local_eggs(path='.'):
"""
Helper utility used by some tests to get the list of egg archive files
in a local directory.
"""
if SETUPTOOLS_VERSION[0] >= 7:
eggs = glob.glob(os.path.join(path, '.eggs', '*.egg'))
else:
eggs = glob.glob('*.egg')
return eggs
import glob
import imp
import os
import pkgutil
import re
import sys
import tarfile
import pytest
from warnings import catch_warnings
from . import reset_setup_helpers, reset_distutils_log # noqa
from . import run_cmd, run_setup, cleanup_import
from astropy_helpers.git_helpers import get_git_devstr
_DEV_VERSION_RE = re.compile(r'\d+\.\d+(?:\.\d+)?\.dev(\d+)')
ASTROPY_HELPERS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
TEST_VERSION_SETUP_PY = """\
#!/usr/bin/env python
import sys
from setuptools import setup
NAME = 'apyhtest_eva'
VERSION = {version!r}
RELEASE = 'dev' not in VERSION
sys.path.insert(0, r'{astropy_helpers_path}')
from astropy_helpers.git_helpers import get_git_devstr
from astropy_helpers.version_helpers import generate_version_py
if not RELEASE:
VERSION += get_git_devstr(False)
generate_version_py(NAME, VERSION, RELEASE, False, uses_git=not RELEASE)
setup(name=NAME, version=VERSION, packages=['apyhtest_eva'])
"""
TEST_VERSION_INIT = """\
try:
from .version import version as __version__
from .version import githash as __githash__
except ImportError:
__version__ = __githash__ = ''
"""
@pytest.fixture
def version_test_package(tmpdir, request):
def make_test_package(version='42.42.dev'):
test_package = tmpdir.mkdir('test_package')
test_package.join('setup.py').write(
TEST_VERSION_SETUP_PY.format(version=version,
astropy_helpers_path=ASTROPY_HELPERS_PATH))
test_package.mkdir('apyhtest_eva').join('__init__.py').write(TEST_VERSION_INIT)
with test_package.as_cwd():
run_cmd('git', ['init'])
run_cmd('git', ['add', '--all'])
run_cmd('git', ['commit', '-m', 'test package'])
if '' in sys.path:
sys.path.remove('')
sys.path.insert(0, '')
def finalize():
cleanup_import('apyhtest_eva')
request.addfinalizer(finalize)
return test_package
return make_test_package
def test_update_git_devstr(version_test_package, capsys):
"""Tests that the commit number in the package's version string updates
after git commits even without re-running setup.py.
"""
# We have to call version_test_package to actually create the package
test_pkg = version_test_package()
with test_pkg.as_cwd():
run_setup('setup.py', ['--version'])
stdout, stderr = capsys.readouterr()
version = stdout.strip()
m = _DEV_VERSION_RE.match(version)
assert m, (
"Stdout did not match the version string pattern:"
"\n\n{0}\n\nStderr:\n\n{1}".format(stdout, stderr))
revcount = int(m.group(1))
import apyhtest_eva
assert apyhtest_eva.__version__ == version
# Make a silly git commit
with open('.test', 'w'):
pass
run_cmd('git', ['add', '.test'])
run_cmd('git', ['commit', '-m', 'test'])
import apyhtest_eva.version
imp.reload(apyhtest_eva.version)
# Previously this checked packagename.__version__, but in order for that to
# be updated we also have to re-import _astropy_init which could be tricky.
# Checking directly that the packagename.version module was updated is
# sufficient:
m = _DEV_VERSION_RE.match(apyhtest_eva.version.version)
assert m
assert int(m.group(1)) == revcount + 1
# This doesn't test astropy_helpers.get_helpers.update_git_devstr directly
# since a copy of that function is made in packagename.version (so that it
# can work without astropy_helpers installed). In order to get test
# coverage on the actual astropy_helpers copy of that function just call it
# directly and compare to the value in packagename
from astropy_helpers.git_helpers import update_git_devstr
newversion = update_git_devstr(version, path=str(test_pkg))
assert newversion == apyhtest_eva.version.version
def test_version_update_in_other_repos(version_test_package, tmpdir):
"""
Regression test for https://github.com/astropy/astropy-helpers/issues/114
and for https://github.com/astropy/astropy-helpers/issues/107
"""
test_pkg = version_test_package()
with test_pkg.as_cwd():
run_setup('setup.py', ['build'])
# Add the path to the test package to sys.path for now
sys.path.insert(0, str(test_pkg))
try:
import apyhtest_eva
m = _DEV_VERSION_RE.match(apyhtest_eva.__version__)
assert m
correct_revcount = int(m.group(1))
with tmpdir.as_cwd():
testrepo = tmpdir.mkdir('testrepo')
testrepo.chdir()
# Create an empty git repo
run_cmd('git', ['init'])
import apyhtest_eva.version
imp.reload(apyhtest_eva.version)
m = _DEV_VERSION_RE.match(apyhtest_eva.version.version)
assert m
assert int(m.group(1)) == correct_revcount
correct_revcount = int(m.group(1))
# Add several commits--more than the revcount for the apyhtest_eva package
for idx in range(correct_revcount + 5):
test_filename = '.test' + str(idx)
testrepo.ensure(test_filename)
run_cmd('git', ['add', test_filename])
run_cmd('git', ['commit', '-m', 'A message'])
import apyhtest_eva.version
imp.reload(apyhtest_eva.version)
m = _DEV_VERSION_RE.match(apyhtest_eva.version.version)
assert m
assert int(m.group(1)) == correct_revcount
correct_revcount = int(m.group(1))
finally:
sys.path.remove(str(test_pkg))
@pytest.mark.parametrize('version', ['1.0.dev', '1.0'])
def test_installed_git_version(version_test_package, version, tmpdir, capsys):
"""
Test for https://github.com/astropy/astropy-helpers/issues/87
Ensures that packages installed with astropy_helpers have a correct copy
of the git hash of the installed commit.
"""
# To test this, it should suffice to build a source dist, unpack it
# somewhere outside the git repository, and then do a build and import
# from the build directory--no need to "install" as such
test_pkg = version_test_package(version)
with test_pkg.as_cwd():
run_setup('setup.py', ['build'])
try:
import apyhtest_eva
githash = apyhtest_eva.__githash__
assert githash and isinstance(githash, str)
# Ensure that it does in fact look like a git hash and not some
# other arbitrary string
assert re.match(r'[0-9a-f]{40}', githash)
finally:
cleanup_import('apyhtest_eva')
run_setup('setup.py', ['sdist', '--dist-dir=dist', '--formats=gztar'])
tgzs = glob.glob(os.path.join('dist', '*.tar.gz'))
assert len(tgzs) == 1
tgz = test_pkg.join(tgzs[0])
build_dir = tmpdir.mkdir('build_dir')
tf = tarfile.open(str(tgz), mode='r:gz')
tf.extractall(str(build_dir))
with build_dir.as_cwd():
pkg_dir = glob.glob('apyhtest_eva-*')[0]
os.chdir(pkg_dir)
with catch_warnings(record=True) as w:
run_setup('setup.py', ['build'])
try:
import apyhtest_eva
loader = pkgutil.get_loader('apyhtest_eva')
# Ensure we are importing the 'packagename' that was just unpacked
# into the build_dir
assert loader.get_filename().startswith(str(build_dir))
assert apyhtest_eva.__githash__ == githash
finally:
cleanup_import('apyhtest_eva')
def test_get_git_devstr(tmpdir):
dirpath = str(tmpdir)
warn_msg = "No git repository present at"
# Verify as much as possible, but avoid dealing with paths on windows
if not sys.platform.startswith('win'):
warn_msg += " '{}'".format(dirpath)
with catch_warnings(record=True) as w:
devstr = get_git_devstr(path=dirpath)
assert devstr == '0'
assert len(w) == 1
assert str(w[0].message).startswith(warn_msg)
import os
import sys
import types
from copy import deepcopy
from importlib import machinery
from distutils.core import Extension
from ..openmp_helpers import add_openmp_flags_if_available, generate_openmp_enabled_py
from ..setup_helpers import _module_state, register_commands
IS_TRAVIS_LINUX = os.environ.get('TRAVIS_OS_NAME', None) == 'linux'
IS_TRAVIS_OSX = os.environ.get('TRAVIS_OS_NAME', None) == 'osx'
IS_APPVEYOR = os.environ.get('APPVEYOR', None) == 'True'
PY3_LT_35 = sys.version_info[0] == 3 and sys.version_info[1] < 5
_state = None
try:
OPENMP_EXPECTED = 'True' == os.environ['OPENMP_EXPECTED']
except KeyError:
raise Exception("The OPENMP_EXPECTED environment variable should be set to "
"True or False and should indicate whether OpenMP should "
"work on your platform.")
def setup_function(function):
global state
state = deepcopy(_module_state)
def teardown_function(function):
_module_state.clear()
_module_state.update(state)
def test_add_openmp_flags_if_available():
register_commands('openmp_testing', '0.0', False)
using_openmp = add_openmp_flags_if_available(Extension('test', []))
# Make sure that on Travis (Linux) and AppVeyor OpenMP does get used (for
# MacOS X usually it will not work but this will depend on the compiler).
# Having this is useful because we'll find out if OpenMP no longer works
# for any reason on platforms on which it does work at the time of writing.
if OPENMP_EXPECTED:
assert using_openmp
else:
assert not using_openmp
def test_generate_openmp_enabled_py():
register_commands('openmp_autogeneration_testing', '0.0', False)
# Test file generation
generate_openmp_enabled_py('')
assert os.path.isfile('openmp_enabled.py')
# Load openmp_enabled file as a module to check the result
loader = machinery.SourceFileLoader('openmp_enabled', 'openmp_enabled.py')
mod = types.ModuleType(loader.name)
loader.exec_module(mod)
is_openmp_enabled = mod.is_openmp_enabled()
# Test is_openmp_enabled()
assert isinstance(is_openmp_enabled, bool)
if OPENMP_EXPECTED:
assert is_openmp_enabled
else:
assert not is_openmp_enabled
import os
import sys
import stat
import shutil
import importlib
import contextlib
import pytest
from textwrap import dedent
from setuptools import Distribution
from ..setup_helpers import get_package_info, register_commands
from ..commands import build_ext
from . import reset_setup_helpers, reset_distutils_log # noqa
from . import run_setup, cleanup_import
ASTROPY_HELPERS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
def _extension_test_package(tmpdir, request, extension_type='c', include_numpy=False):
"""Creates a simple test package with an extension module."""
test_pkg = tmpdir.mkdir('test_pkg')
test_pkg.mkdir('apyhtest_eva').ensure('__init__.py')
# TODO: It might be later worth making this particular test package into a
# reusable fixture for other build_ext tests
if extension_type in ('c', 'both'):
# A minimal C extension for testing
test_pkg.join('apyhtest_eva', 'unit01.c').write(dedent("""\
#include <Python.h>
#ifndef PY3K
#if PY_MAJOR_VERSION >= 3
#define PY3K 1
#else
#define PY3K 0
#endif
#endif
#if PY3K
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"unit01",
NULL,
-1,
NULL
};
PyMODINIT_FUNC
PyInit_unit01(void) {
return PyModule_Create(&moduledef);
}
#else
PyMODINIT_FUNC
initunit01(void) {
Py_InitModule3("unit01", NULL, NULL);
}
#endif
"""))
if extension_type in ('pyx', 'both'):
# A minimal Cython extension for testing
test_pkg.join('apyhtest_eva', 'unit02.pyx').write(dedent("""\
print("Hello cruel angel.")
"""))
if extension_type == 'c':
extensions = ['unit01.c']
elif extension_type == 'pyx':
extensions = ['unit02.pyx']
elif extension_type == 'both':
extensions = ['unit01.c', 'unit02.pyx']
include_dirs = ['numpy'] if include_numpy else []
extensions_list = [
"Extension('apyhtest_eva.{0}', [join('apyhtest_eva', '{1}')], include_dirs={2})".format(
os.path.splitext(extension)[0], extension, include_dirs)
for extension in extensions]
test_pkg.join('apyhtest_eva', 'setup_package.py').write(dedent("""\
from setuptools import Extension
from os.path import join
def get_extensions():
return [{0}]
""".format(', '.join(extensions_list))))
test_pkg.join('setup.py').write(dedent("""\
import sys
from os.path import join
from setuptools import setup
sys.path.insert(0, r'{astropy_helpers_path}')
from astropy_helpers.setup_helpers import register_commands
from astropy_helpers.setup_helpers import get_package_info
from astropy_helpers.version_helpers import generate_version_py
if '--no-cython' in sys.argv:
from astropy_helpers.commands import build_ext
build_ext.should_build_with_cython = lambda *args: False
sys.argv.remove('--no-cython')
NAME = 'apyhtest_eva'
VERSION = '0.1'
RELEASE = True
cmdclassd = register_commands(NAME, VERSION, RELEASE)
generate_version_py(NAME, VERSION, RELEASE, False, False)
package_info = get_package_info()
setup(
name=NAME,
version=VERSION,
cmdclass=cmdclassd,
**package_info
)
""".format(astropy_helpers_path=ASTROPY_HELPERS_PATH)))
if '' in sys.path:
sys.path.remove('')
sys.path.insert(0, '')
def finalize():
cleanup_import('apyhtest_eva')
request.addfinalizer(finalize)
return test_pkg
@pytest.fixture
def extension_test_package(tmpdir, request):
return _extension_test_package(tmpdir, request, extension_type='both')
@pytest.fixture
def c_extension_test_package(tmpdir, request):
# Check whether numpy is installed in the test environment
has_numpy = bool(importlib.util.find_spec('numpy'))
return _extension_test_package(tmpdir, request, extension_type='c',
include_numpy=has_numpy)
@pytest.fixture
def pyx_extension_test_package(tmpdir, request):
return _extension_test_package(tmpdir, request, extension_type='pyx')
def test_cython_autoextensions(tmpdir):
"""
Regression test for https://github.com/astropy/astropy-helpers/pull/19
Ensures that Cython extensions in sub-packages are discovered and built
only once.
"""
# Make a simple test package
test_pkg = tmpdir.mkdir('test_pkg')
test_pkg.mkdir('yoda').mkdir('luke')
test_pkg.ensure('yoda', '__init__.py')
test_pkg.ensure('yoda', 'luke', '__init__.py')
test_pkg.join('yoda', 'luke', 'dagobah.pyx').write(
"""def testfunc(): pass""")
# Required, currently, for get_package_info to work
register_commands('yoda', '0.0', False, srcdir=str(test_pkg))
package_info = get_package_info(str(test_pkg))
assert len(package_info['ext_modules']) == 1
assert package_info['ext_modules'][0].name == 'yoda.luke.dagobah'
def test_compiler_module(capsys, c_extension_test_package):
"""
Test ensuring that the compiler module is built and installed for packages
that have extension modules.
"""
test_pkg = c_extension_test_package
install_temp = test_pkg.mkdir('install_temp')
with test_pkg.as_cwd():
# This is one of the simplest ways to install just a package into a
# test directory
run_setup('setup.py',
['install',
'--single-version-externally-managed',
'--install-lib={0}'.format(install_temp),
'--record={0}'.format(install_temp.join('record.txt'))])
stdout, stderr = capsys.readouterr()
assert "No git repository present at" in stderr
with install_temp.as_cwd():
import apyhtest_eva
# Make sure we imported the apyhtest_eva package from the correct place
dirname = os.path.abspath(os.path.dirname(apyhtest_eva.__file__))
assert dirname == str(install_temp.join('apyhtest_eva'))
import apyhtest_eva._compiler
import apyhtest_eva.version
assert apyhtest_eva.version.compiler == apyhtest_eva._compiler.compiler
assert apyhtest_eva.version.compiler != 'unknown'
def test_no_cython_buildext(capsys, c_extension_test_package, monkeypatch):
"""
Regression test for https://github.com/astropy/astropy-helpers/pull/35
This tests the custom build_ext command installed by astropy_helpers when
used with a project that has no Cython extensions (but does have one or
more normal C extensions).
"""
test_pkg = c_extension_test_package
with test_pkg.as_cwd():
run_setup('setup.py', ['build_ext', '--inplace', '--no-cython'])
stdout, stderr = capsys.readouterr()
assert "No git repository present at" in stderr
sys.path.insert(0, str(test_pkg))
try:
import apyhtest_eva.unit01
dirname = os.path.abspath(os.path.dirname(apyhtest_eva.unit01.__file__))
assert dirname == str(test_pkg.join('apyhtest_eva'))
finally:
sys.path.remove(str(test_pkg))
def test_missing_cython_c_files(capsys, pyx_extension_test_package, monkeypatch):
"""
Regression test for https://github.com/astropy/astropy-helpers/pull/181
Test failure mode when building a package that has Cython modules, but
where Cython is not installed and the generated C files are missing.
"""
test_pkg = pyx_extension_test_package
with test_pkg.as_cwd():
with pytest.raises(SystemExit):
run_setup('setup.py', ['build_ext', '--inplace', '--no-cython'])
stdout, stderr = capsys.readouterr()
assert "No git repository present at" in stderr
msg = ('Could not find C/C++ file '
'{0}.(c/cpp)'.format('apyhtest_eva/unit02'.replace('/', os.sep)))
assert msg in stderr
@pytest.mark.parametrize('mode', ['cli', 'cli-w', 'deprecated', 'cli-l'])
def test_build_docs(capsys, tmpdir, mode):
"""
Test for build_docs
"""
test_pkg = tmpdir.mkdir('test_pkg')
test_pkg.mkdir('mypackage')
test_pkg.join('mypackage').join('__init__.py').write(dedent("""\
def test_function():
pass
class A():
pass
class B(A):
pass
"""))
test_pkg.mkdir('docs')
docs_dir = test_pkg.join('docs')
docs_dir.join('conf.py').write(dedent("""\
import warnings
with warnings.catch_warnings(): # ignore matplotlib warning
warnings.simplefilter("ignore")
from sphinx_astropy.conf import *
exclude_patterns.append('_templates')
suppress_warnings = ['app.add_directive', 'app.add_node', 'app.add_role']
"""))
docs_dir.join('index.rst').write(dedent("""\
.. automodapi:: mypackage
:no-inheritance-diagram:
"""))
test_pkg.join('setup.py').write(dedent("""\
import sys
sys.path.insert(0, r'{astropy_helpers_path}')
from os.path import join
from setuptools import setup, Extension
from astropy_helpers.setup_helpers import register_commands, get_package_info
NAME = 'mypackage'
VERSION = 0.1
RELEASE = True
cmdclassd = register_commands(NAME, VERSION, RELEASE)
setup(
name=NAME,
version=VERSION,
cmdclass=cmdclassd,
**get_package_info()
)
""".format(astropy_helpers_path=ASTROPY_HELPERS_PATH)))
with test_pkg.as_cwd():
if mode == 'cli':
run_setup('setup.py', ['build_docs'])
elif mode == 'cli-w':
run_setup('setup.py', ['build_docs', '-w'])
elif mode == 'cli-l':
run_setup('setup.py', ['build_docs', '-l'])
elif mode == 'deprecated':
run_setup('setup.py', ['build_sphinx'])
stdout, stderr = capsys.readouterr()
assert 'AstropyDeprecationWarning' in stderr
assert os.path.exists(docs_dir.join('_build', 'html', 'index.html').strpath)
def test_command_hooks(tmpdir, capsys):
"""A basic test for pre- and post-command hooks."""
test_pkg = tmpdir.mkdir('test_pkg')
test_pkg.mkdir('_welltall_')
test_pkg.join('_welltall_', '__init__.py').ensure()
# Create a setup_package module with a couple of command hooks in it
test_pkg.join('_welltall_', 'setup_package.py').write(dedent("""\
def pre_build_hook(cmd_obj):
print('Hello build!')
def post_build_hook(cmd_obj):
print('Goodbye build!')
"""))
# A simple setup.py for the test package--running register_commands should
# discover and enable the command hooks
test_pkg.join('setup.py').write(dedent("""\
import sys
from os.path import join
from setuptools import setup, Extension
sys.path.insert(0, r'{astropy_helpers_path}')
from astropy_helpers.setup_helpers import register_commands, get_package_info
NAME = '_welltall_'
VERSION = 0.1
RELEASE = True
cmdclassd = register_commands(NAME, VERSION, RELEASE)
setup(
name=NAME,
version=VERSION,
cmdclass=cmdclassd
)
""".format(astropy_helpers_path=ASTROPY_HELPERS_PATH)))
with test_pkg.as_cwd():
try:
run_setup('setup.py', ['build'])
finally:
cleanup_import('_welltall_')
stdout, stderr = capsys.readouterr()
want = dedent("""\
running build
running pre_hook from _welltall_.setup_package for build command
Hello build!
running post_hook from _welltall_.setup_package for build command
Goodbye build!
""").strip()
assert want in stdout.replace('\r\n', '\n').replace('\r', '\n')
def test_adjust_compiler(monkeypatch, tmpdir):
"""
Regression test for https://github.com/astropy/astropy-helpers/issues/182
"""
from distutils import ccompiler, sysconfig
class MockLog(object):
def __init__(self):
self.messages = []
def warn(self, message):
self.messages.append(message)
good = tmpdir.join('gcc-good')
good.write(dedent("""\
#!{python}
import sys
print('gcc 4.10')
sys.exit(0)
""".format(python=sys.executable)))
good.chmod(stat.S_IRUSR | stat.S_IEXEC)
# A "compiler" that reports itself to be a version of Apple's llvm-gcc
# which is broken
bad = tmpdir.join('gcc-bad')
bad.write(dedent("""\
#!{python}
import sys
print('i686-apple-darwin-llvm-gcc-4.2')
sys.exit(0)
""".format(python=sys.executable)))
bad.chmod(stat.S_IRUSR | stat.S_IEXEC)
# A "compiler" that doesn't even know its identity (this reproduces the bug
# in #182)
ugly = tmpdir.join('gcc-ugly')
ugly.write(dedent("""\
#!{python}
import sys
sys.exit(1)
""".format(python=sys.executable)))
ugly.chmod(stat.S_IRUSR | stat.S_IEXEC)
# Scripts with shebang lines don't work implicitly in Windows when passed
# to subprocess.Popen, so...
if 'win' in sys.platform:
good = ' '.join((sys.executable, str(good)))
bad = ' '.join((sys.executable, str(bad)))
ugly = ' '.join((sys.executable, str(ugly)))
dist = Distribution({})
cmd_cls = build_ext.generate_build_ext_command('astropy', False)
cmd = cmd_cls(dist)
adjust_compiler = cmd._adjust_compiler
@contextlib.contextmanager
def test_setup():
log = MockLog()
monkeypatch.setattr(build_ext, 'log', log)
yield log
monkeypatch.undo()
@contextlib.contextmanager
def compiler_setter_with_environ(compiler):
monkeypatch.setenv('CC', compiler)
with test_setup() as log:
yield log
monkeypatch.undo()
@contextlib.contextmanager
def compiler_setter_with_sysconfig(compiler):
monkeypatch.setattr(ccompiler, 'get_default_compiler', lambda: 'unix')
monkeypatch.setattr(sysconfig, 'get_config_var', lambda v: compiler)
old_cc = os.environ.get('CC')
if old_cc is not None:
del os.environ['CC']
with test_setup() as log:
yield log
monkeypatch.undo()
monkeypatch.undo()
monkeypatch.undo()
if old_cc is not None:
os.environ['CC'] = old_cc
compiler_setters = (compiler_setter_with_environ,
compiler_setter_with_sysconfig)
for compiler_setter in compiler_setters:
with compiler_setter(str(good)):
# Should have no side-effects
adjust_compiler()
with compiler_setter(str(ugly)):
# Should just pass without complaint, since we can't determine
# anything about the compiler anyways
adjust_compiler()
# In the following tests we check the log messages just to ensure that the
# failures occur on the correct code paths for these cases
with compiler_setter_with_environ(str(bad)) as log:
with pytest.raises(SystemExit):
adjust_compiler()
assert len(log.messages) == 1
assert 'will fail to compile' in log.messages[0]
with compiler_setter_with_sysconfig(str(bad)):
adjust_compiler()
assert 'CC' in os.environ and os.environ['CC'] == 'clang'
with compiler_setter_with_environ('bogus') as log:
with pytest.raises(SystemExit):
# Missing compiler?
adjust_compiler()
assert len(log.messages) == 1
assert 'cannot be found or executed' in log.messages[0]
with compiler_setter_with_sysconfig('bogus') as log:
with pytest.raises(SystemExit):
# Missing compiler?
adjust_compiler()
assert len(log.messages) == 1
assert 'The C compiler used to compile Python' in log.messages[0]
def test_invalid_package_exclusion(tmpdir, capsys):
module_name = 'foobar'
setup_header = dedent("""\
import sys
from os.path import join
from setuptools import setup, Extension
sys.path.insert(0, r'{astropy_helpers_path}')
from astropy_helpers.setup_helpers import register_commands, \\
get_package_info, add_exclude_packages
NAME = {module_name!r}
VERSION = 0.1
RELEASE = True
""".format(module_name=module_name, astropy_helpers_path=ASTROPY_HELPERS_PATH))
setup_footer = dedent("""\
setup(
name=NAME,
version=VERSION,
cmdclass=cmdclassd,
**package_info
)
""")
# Test error when using add_package_excludes out of order
error_commands = dedent("""\
cmdclassd = register_commands(NAME, VERSION, RELEASE)
package_info = get_package_info()
add_exclude_packages(['tests*'])
""")
error_pkg = tmpdir.mkdir('error_pkg')
error_pkg.join('setup.py').write(
setup_header + error_commands + setup_footer)
with error_pkg.as_cwd():
with pytest.raises(SystemExit):
run_setup('setup.py', ['build'])
stdout, stderr = capsys.readouterr()
assert "RuntimeError" in stderr
# Test warning when using deprecated exclude parameter
warn_commands = dedent("""\
cmdclassd = register_commands(NAME, VERSION, RELEASE)
package_info = get_package_info(exclude=['test*'])
""")
warn_pkg = tmpdir.mkdir('warn_pkg')
warn_pkg.join('setup.py').write(
setup_header + warn_commands + setup_footer)
with warn_pkg.as_cwd():
run_setup('setup.py', ['build'])
stdout, stderr = capsys.readouterr()
assert 'AstropyDeprecationWarning' in stderr
import os
from ..utils import find_data_files
def test_find_data_files(tmpdir):
data = tmpdir.mkdir('data')
sub1 = data.mkdir('sub1')
sub2 = data.mkdir('sub2')
sub3 = sub1.mkdir('sub3')
for directory in (data, sub1, sub2, sub3):
filename = directory.join('data.dat').strpath
with open(filename, 'w') as f:
f.write('test')
filenames = find_data_files(data.strpath, '**/*.dat')
filenames = sorted(os.path.relpath(x, data.strpath) for x in filenames)
assert filenames[0] == os.path.join('data.dat')
assert filenames[1] == os.path.join('sub1', 'data.dat')
assert filenames[2] == os.path.join('sub1', 'sub3', 'data.dat')
assert filenames[3] == os.path.join('sub2', 'data.dat')
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from __future__ import absolute_import, unicode_literals
import contextlib
import functools
import imp
import inspect
import os
import sys
import glob
import textwrap
import types
import warnings
from importlib import machinery as import_machinery
# Note: The following Warning subclasses are simply copies of the Warnings in
# Astropy of the same names.
class AstropyWarning(Warning):
"""
The base warning class from which all Astropy warnings should inherit.
Any warning inheriting from this class is handled by the Astropy logger.
"""
class AstropyDeprecationWarning(AstropyWarning):
"""
A warning class to indicate a deprecated feature.
"""
class AstropyPendingDeprecationWarning(PendingDeprecationWarning,
AstropyWarning):
"""
A warning class to indicate a soon-to-be deprecated feature.
"""
def _get_platlib_dir(cmd):
"""
Given a build command, return the name of the appropriate platform-specific
build subdirectory directory (e.g. build/lib.linux-x86_64-2.7)
"""
plat_specifier = '.{0}-{1}'.format(cmd.plat_name, sys.version[0:3])
return os.path.join(cmd.build_base, 'lib' + plat_specifier)
def get_numpy_include_path():
"""
Gets the path to the numpy headers.
"""
# We need to go through this nonsense in case setuptools
# downloaded and installed Numpy for us as part of the build or
# install, since Numpy may still think it's in "setup mode", when
# in fact we're ready to use it to build astropy now.
import builtins
if hasattr(builtins, '__NUMPY_SETUP__'):
del builtins.__NUMPY_SETUP__
import imp
import numpy
imp.reload(numpy)
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
return numpy_include
class _DummyFile(object):
"""A noop writeable object."""
errors = ''
def write(self, s):
pass
def flush(self):
pass
@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
if sys.platform == 'win32':
import ctypes
def _has_hidden_attribute(filepath):
"""
Returns True if the given filepath has the hidden attribute on
MS-Windows. Based on a post here:
http://stackoverflow.com/questions/284115/cross-platform-hidden-file-detection
"""
if isinstance(filepath, bytes):
filepath = filepath.decode(sys.getfilesystemencoding())
try:
attrs = ctypes.windll.kernel32.GetFileAttributesW(filepath)
assert attrs != -1
result = bool(attrs & 2)
except (AttributeError, AssertionError):
result = False
return result
else:
def _has_hidden_attribute(filepath):
return False
def is_path_hidden(filepath):
"""
Determines if a given file or directory is hidden.
Parameters
----------
filepath : str
The path to a file or directory
Returns
-------
hidden : bool
Returns `True` if the file is hidden
"""
name = os.path.basename(os.path.abspath(filepath))
if isinstance(name, bytes):
is_dotted = name.startswith(b'.')
else:
is_dotted = name.startswith('.')
return is_dotted or _has_hidden_attribute(filepath)
def walk_skip_hidden(top, onerror=None, followlinks=False):
"""
A wrapper for `os.walk` that skips hidden files and directories.
This function does not have the parameter `topdown` from
`os.walk`: the directories must always be recursed top-down when
using this function.
See also
--------
os.walk : For a description of the parameters
"""
for root, dirs, files in os.walk(
top, topdown=True, onerror=onerror,
followlinks=followlinks):
# These lists must be updated in-place so os.walk will skip
# hidden directories
dirs[:] = [d for d in dirs if not is_path_hidden(d)]
files[:] = [f for f in files if not is_path_hidden(f)]
yield root, dirs, files
def write_if_different(filename, data):
"""Write `data` to `filename`, if the content of the file is different.
Parameters
----------
filename : str
The file name to be written to.
data : bytes
The data to be written to `filename`.
"""
assert isinstance(data, bytes)
if os.path.exists(filename):
with open(filename, 'rb') as fd:
original_data = fd.read()
else:
original_data = None
if original_data != data:
with open(filename, 'wb') as fd:
fd.write(data)
def import_file(filename, name=None):
"""
Imports a module from a single file as if it doesn't belong to a
particular package.
The returned module will have the optional ``name`` if given, or else
a name generated from the filename.
"""
# Specifying a traditional dot-separated fully qualified name here
# results in a number of "Parent module 'astropy' not found while
# handling absolute import" warnings. Using the same name, the
# namespaces of the modules get merged together. So, this
# generates an underscore-separated name which is more likely to
# be unique, and it doesn't really matter because the name isn't
# used directly here anyway.
mode = 'r'
if name is None:
basename = os.path.splitext(filename)[0]
name = '_'.join(os.path.relpath(basename).split(os.sep)[1:])
if import_machinery:
loader = import_machinery.SourceFileLoader(name, filename)
mod = loader.load_module()
else:
with open(filename, mode) as fd:
mod = imp.load_module(name, fd, filename, ('.py', mode, 1))
return mod
def resolve_name(name):
"""Resolve a name like ``module.object`` to an object and return it.
Raise `ImportError` if the module or name is not found.
"""
parts = name.split('.')
cursor = len(parts) - 1
module_name = parts[:cursor]
attr_name = parts[-1]
while cursor > 0:
try:
ret = __import__('.'.join(module_name), fromlist=[attr_name])
break
except ImportError:
if cursor == 0:
raise
cursor -= 1
module_name = parts[:cursor]
attr_name = parts[cursor]
ret = ''
for part in parts[cursor:]:
try:
ret = getattr(ret, part)
except AttributeError:
raise ImportError(name)
return ret
def extends_doc(extended_func):
"""
A function decorator for use when wrapping an existing function but adding
additional functionality. This copies the docstring from the original
function, and appends to it (along with a newline) the docstring of the
wrapper function.
Examples
--------
>>> def foo():
... '''Hello.'''
...
>>> @extends_doc(foo)
... def bar():
... '''Goodbye.'''
...
>>> print(bar.__doc__)
Hello.
Goodbye.
"""
def decorator(func):
if not (extended_func.__doc__ is None or func.__doc__ is None):
func.__doc__ = '\n\n'.join([extended_func.__doc__.rstrip('\n'),
func.__doc__.lstrip('\n')])
return func
return decorator
# Duplicated from astropy.utils.decorators.deprecated
# When fixing issues in this function fix them in astropy first, then
# port the fixes over to astropy-helpers
def deprecated(since, message='', name='', alternative='', pending=False,
obj_type=None):
"""
Used to mark a function or class as deprecated.
To mark an attribute as deprecated, use `deprecated_attribute`.
Parameters
----------
since : str
The release at which this API became deprecated. This is
required.
message : str, optional
Override the default deprecation message. The format
specifier ``func`` may be used for the name of the function,
and ``alternative`` may be used in the deprecation message
to insert the name of an alternative to the deprecated
function. ``obj_type`` may be used to insert a friendly name
for the type of object being deprecated.
name : str, optional
The name of the deprecated function or class; if not provided
the name is automatically determined from the passed in
function or class, though this is useful in the case of
renamed functions, where the new function is just assigned to
the name of the deprecated function. For example::
def new_function():
...
oldFunction = new_function
alternative : str, optional
An alternative function or class name that the user may use in
place of the deprecated object. The deprecation warning will
tell the user about this alternative if provided.
pending : bool, optional
If True, uses a AstropyPendingDeprecationWarning instead of a
AstropyDeprecationWarning.
obj_type : str, optional
The type of this object, if the automatically determined one
needs to be overridden.
"""
method_types = (classmethod, staticmethod, types.MethodType)
def deprecate_doc(old_doc, message):
"""
Returns a given docstring with a deprecation message prepended
to it.
"""
if not old_doc:
old_doc = ''
old_doc = textwrap.dedent(old_doc).strip('\n')
new_doc = (('\n.. deprecated:: {since}'
'\n {message}\n\n'.format(
**{'since': since, 'message': message.strip()})) + old_doc)
if not old_doc:
# This is to prevent a spurious 'unexpected unindent' warning from
# docutils when the original docstring was blank.
new_doc += r'\ '
return new_doc
def get_function(func):
"""
Given a function or classmethod (or other function wrapper type), get
the function object.
"""
if isinstance(func, method_types):
func = func.__func__
return func
def deprecate_function(func, message):
"""
Returns a wrapped function that displays an
``AstropyDeprecationWarning`` when it is called.
"""
if isinstance(func, method_types):
func_wrapper = type(func)
else:
func_wrapper = lambda f: f
func = get_function(func)
def deprecated_func(*args, **kwargs):
if pending:
category = AstropyPendingDeprecationWarning
else:
category = AstropyDeprecationWarning
warnings.warn(message, category, stacklevel=2)
return func(*args, **kwargs)
# If this is an extension function, we can't call
# functools.wraps on it, but we normally don't care.
# This crazy way to get the type of a wrapper descriptor is
# straight out of the Python 3.3 inspect module docs.
if type(func) is not type(str.__dict__['__add__']): # nopep8
deprecated_func = functools.wraps(func)(deprecated_func)
deprecated_func.__doc__ = deprecate_doc(
deprecated_func.__doc__, message)
return func_wrapper(deprecated_func)
def deprecate_class(cls, message):
"""
Update the docstring and wrap the ``__init__`` in-place (or ``__new__``
if the class or any of the bases overrides ``__new__``) so it will give
a deprecation warning when an instance is created.
This won't work for extension classes because these can't be modified
in-place and the alternatives don't work in the general case:
- Using a new class that looks and behaves like the original doesn't
work because the __new__ method of extension types usually makes sure
that it's the same class or a subclass.
- Subclassing the class and return the subclass can lead to problems
with pickle and will look weird in the Sphinx docs.
"""
cls.__doc__ = deprecate_doc(cls.__doc__, message)
if cls.__new__ is object.__new__:
cls.__init__ = deprecate_function(get_function(cls.__init__), message)
else:
cls.__new__ = deprecate_function(get_function(cls.__new__), message)
return cls
def deprecate(obj, message=message, name=name, alternative=alternative,
pending=pending):
if obj_type is None:
if isinstance(obj, type):
obj_type_name = 'class'
elif inspect.isfunction(obj):
obj_type_name = 'function'
elif inspect.ismethod(obj) or isinstance(obj, method_types):
obj_type_name = 'method'
else:
obj_type_name = 'object'
else:
obj_type_name = obj_type
if not name:
name = get_function(obj).__name__
altmessage = ''
if not message or type(message) is type(deprecate):
if pending:
message = ('The {func} {obj_type} will be deprecated in a '
'future version.')
else:
message = ('The {func} {obj_type} is deprecated and may '
'be removed in a future version.')
if alternative:
altmessage = '\n Use {} instead.'.format(alternative)
message = ((message.format(**{
'func': name,
'name': name,
'alternative': alternative,
'obj_type': obj_type_name})) +
altmessage)
if isinstance(obj, type):
return deprecate_class(obj, message)
else:
return deprecate_function(obj, message)
if type(message) is type(deprecate):
return deprecate(message)
return deprecate
def deprecated_attribute(name, since, message=None, alternative=None,
pending=False):
"""
Used to mark a public attribute as deprecated. This creates a
property that will warn when the given attribute name is accessed.
To prevent the warning (i.e. for internal code), use the private
name for the attribute by prepending an underscore
(i.e. ``self._name``).
Parameters
----------
name : str
The name of the deprecated attribute.
since : str
The release at which this API became deprecated. This is
required.
message : str, optional
Override the default deprecation message. The format
specifier ``name`` may be used for the name of the attribute,
and ``alternative`` may be used in the deprecation message
to insert the name of an alternative to the deprecated
function.
alternative : str, optional
An alternative attribute that the user may use in place of the
deprecated attribute. The deprecation warning will tell the
user about this alternative if provided.
pending : bool, optional
If True, uses a AstropyPendingDeprecationWarning instead of a
AstropyDeprecationWarning.
Examples
--------
::
class MyClass:
# Mark the old_name as deprecated
old_name = misc.deprecated_attribute('old_name', '0.1')
def method(self):
self._old_name = 42
"""
private_name = '_' + name
@deprecated(since, name=name, obj_type='attribute')
def get(self):
return getattr(self, private_name)
@deprecated(since, name=name, obj_type='attribute')
def set(self, val):
setattr(self, private_name, val)
@deprecated(since, name=name, obj_type='attribute')
def delete(self):
delattr(self, private_name)
return property(get, set, delete)
def minversion(module, version, inclusive=True, version_path='__version__'):
"""
Returns `True` if the specified Python module satisfies a minimum version
requirement, and `False` if not.
By default this uses `pkg_resources.parse_version` to do the version
comparison if available. Otherwise it falls back on
`distutils.version.LooseVersion`.
Parameters
----------
module : module or `str`
An imported module of which to check the version, or the name of
that module (in which case an import of that module is attempted--
if this fails `False` is returned).
version : `str`
The version as a string that this module must have at a minimum (e.g.
``'0.12'``).
inclusive : `bool`
The specified version meets the requirement inclusively (i.e. ``>=``)
as opposed to strictly greater than (default: `True`).
version_path : `str`
A dotted attribute path to follow in the module for the version.
Defaults to just ``'__version__'``, which should work for most Python
modules.
Examples
--------
>>> import astropy
>>> minversion(astropy, '0.4.4')
True
"""
if isinstance(module, types.ModuleType):
module_name = module.__name__
elif isinstance(module, str):
module_name = module
try:
module = resolve_name(module_name)
except ImportError:
return False
else:
raise ValueError('module argument must be an actual imported '
'module, or the import name of the module; '
'got {0!r}'.format(module))
if '.' not in version_path:
have_version = getattr(module, version_path)
else:
have_version = resolve_name('.'.join([module.__name__, version_path]))
try:
from pkg_resources import parse_version
except ImportError:
from distutils.version import LooseVersion as parse_version
if inclusive:
return parse_version(have_version) >= parse_version(version)
else:
return parse_version(have_version) > parse_version(version)
# Copy of the classproperty decorator from astropy.utils.decorators
class classproperty(property):
"""
Similar to `property`, but allows class-level properties. That is,
a property whose getter is like a `classmethod`.
The wrapped method may explicitly use the `classmethod` decorator (which
must become before this decorator), or the `classmethod` may be omitted
(it is implicit through use of this decorator).
.. note::
classproperty only works for *read-only* properties. It does not
currently allow writeable/deleteable properties, due to subtleties of how
Python descriptors work. In order to implement such properties on a class
a metaclass for that class must be implemented.
Parameters
----------
fget : callable
The function that computes the value of this property (in particular,
the function when this is used as a decorator) a la `property`.
doc : str, optional
The docstring for the property--by default inherited from the getter
function.
lazy : bool, optional
If True, caches the value returned by the first call to the getter
function, so that it is only called once (used for lazy evaluation
of an attribute). This is analogous to `lazyproperty`. The ``lazy``
argument can also be used when `classproperty` is used as a decorator
(see the third example below). When used in the decorator syntax this
*must* be passed in as a keyword argument.
Examples
--------
::
>>> class Foo:
... _bar_internal = 1
... @classproperty
... def bar(cls):
... return cls._bar_internal + 1
...
>>> Foo.bar
2
>>> foo_instance = Foo()
>>> foo_instance.bar
2
>>> foo_instance._bar_internal = 2
>>> foo_instance.bar # Ignores instance attributes
2
As previously noted, a `classproperty` is limited to implementing
read-only attributes::
>>> class Foo:
... _bar_internal = 1
... @classproperty
... def bar(cls):
... return cls._bar_internal
... @bar.setter
... def bar(cls, value):
... cls._bar_internal = value
...
Traceback (most recent call last):
...
NotImplementedError: classproperty can only be read-only; use a
metaclass to implement modifiable class-level properties
When the ``lazy`` option is used, the getter is only called once::
>>> class Foo:
... @classproperty(lazy=True)
... def bar(cls):
... print("Performing complicated calculation")
... return 1
...
>>> Foo.bar
Performing complicated calculation
1
>>> Foo.bar
1
If a subclass inherits a lazy `classproperty` the property is still
re-evaluated for the subclass::
>>> class FooSub(Foo):
... pass
...
>>> FooSub.bar
Performing complicated calculation
1
>>> FooSub.bar
1
"""
def __new__(cls, fget=None, doc=None, lazy=False):
if fget is None:
# Being used as a decorator--return a wrapper that implements
# decorator syntax
def wrapper(func):
return cls(func, lazy=lazy)
return wrapper
return super().__new__(cls)
def __init__(self, fget, doc=None, lazy=False):
self._lazy = lazy
if lazy:
self._cache = {}
fget = self._wrap_fget(fget)
super().__init__(fget=fget, doc=doc)
# There is a buglet in Python where self.__doc__ doesn't
# get set properly on instances of property subclasses if
# the doc argument was used rather than taking the docstring
# from fget
# Related Python issue: https://bugs.python.org/issue24766
if doc is not None:
self.__doc__ = doc
def __get__(self, obj, objtype):
if self._lazy and objtype in self._cache:
return self._cache[objtype]
# The base property.__get__ will just return self here;
# instead we pass objtype through to the original wrapped
# function (which takes the class as its sole argument)
val = self.fget.__wrapped__(objtype)
if self._lazy:
self._cache[objtype] = val
return val
def getter(self, fget):
return super().getter(self._wrap_fget(fget))
def setter(self, fset):
raise NotImplementedError(
"classproperty can only be read-only; use a metaclass to "
"implement modifiable class-level properties")
def deleter(self, fdel):
raise NotImplementedError(
"classproperty can only be read-only; use a metaclass to "
"implement modifiable class-level properties")
@staticmethod
def _wrap_fget(orig_fget):
if isinstance(orig_fget, classmethod):
orig_fget = orig_fget.__func__
# Using stock functools.wraps instead of the fancier version
# found later in this module, which is overkill for this purpose
@functools.wraps(orig_fget)
def fget(obj):
return orig_fget(obj.__class__)
return fget
def find_data_files(package, pattern):
"""
Include files matching ``pattern`` inside ``package``.
Parameters
----------
package : str
The package inside which to look for data files
pattern : str
Pattern (glob-style) to match for the data files (e.g. ``*.dat``).
This supports the``**``recursive syntax. For example, ``**/*.fits``
matches all files ending with ``.fits`` recursively. Only one
instance of ``**`` can be included in the pattern.
"""
return glob.glob(os.path.join(package, pattern), recursive=True)
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Utilities for generating the version string for Astropy (or an affiliated
package) and the version.py module, which contains version info for the
package.
Within the generated astropy.version module, the `major`, `minor`, and `bugfix`
variables hold the respective parts of the version number (bugfix is '0' if
absent). The `release` variable is True if this is a release, and False if this
is a development version of astropy. For the actual version string, use::
from astropy.version import version
or::
from astropy import __version__
"""
from __future__ import division
import datetime
import imp
import os
import pkgutil
import sys
import time
from distutils import log
from importlib import invalidate_caches
import pkg_resources
from . import git_helpers
from .distutils_helpers import is_distutils_display_option
def _version_split(version):
"""
Split a version string into major, minor, and bugfix numbers. If any of
those numbers are missing the default is zero. Any pre/post release
modifiers are ignored.
Examples
========
>>> _version_split('1.2.3')
(1, 2, 3)
>>> _version_split('1.2')
(1, 2, 0)
>>> _version_split('1.2rc1')
(1, 2, 0)
>>> _version_split('1')
(1, 0, 0)
>>> _version_split('')
(0, 0, 0)
"""
parsed_version = pkg_resources.parse_version(version)
if hasattr(parsed_version, 'base_version'):
# New version parsing for setuptools >= 8.0
if parsed_version.base_version:
parts = [int(part)
for part in parsed_version.base_version.split('.')]
else:
parts = []
else:
parts = []
for part in parsed_version:
if part.startswith('*'):
# Ignore any .dev, a, b, rc, etc.
break
parts.append(int(part))
if len(parts) < 3:
parts += [0] * (3 - len(parts))
# In principle a version could have more parts (like 1.2.3.4) but we only
# support <major>.<minor>.<micro>
return tuple(parts[:3])
# This is used by setup.py to create a new version.py - see that file for
# details. Note that the imports have to be absolute, since this is also used
# by affiliated packages.
_FROZEN_VERSION_PY_TEMPLATE = """
# Autogenerated by {packagetitle}'s setup.py on {timestamp!s} UTC
from __future__ import unicode_literals
import datetime
{header}
major = {major}
minor = {minor}
bugfix = {bugfix}
version_info = (major, minor, bugfix)
release = {rel}
timestamp = {timestamp!r}
debug = {debug}
astropy_helpers_version = "{ahver}"
try:
from ._compiler import compiler
except ImportError:
compiler = "unknown"
try:
from .cython_version import cython_version
except ImportError:
cython_version = "unknown"
"""[1:]
_FROZEN_VERSION_PY_WITH_GIT_HEADER = """
{git_helpers}
_packagename = "{packagename}"
_last_generated_version = "{verstr}"
_last_githash = "{githash}"
# Determine where the source code for this module
# lives. If __file__ is not a filesystem path then
# it is assumed not to live in a git repo at all.
if _get_repo_path(__file__, levels=len(_packagename.split('.'))):
version = update_git_devstr(_last_generated_version, path=__file__)
githash = get_git_devstr(sha=True, show_warning=False,
path=__file__) or _last_githash
else:
# The file does not appear to live in a git repo so don't bother
# invoking git
version = _last_generated_version
githash = _last_githash
"""[1:]
_FROZEN_VERSION_PY_STATIC_HEADER = """
version = "{verstr}"
githash = "{githash}"
"""[1:]
def _get_version_py_str(packagename, version, githash, release, debug,
uses_git=True):
try:
from astropy_helpers import __version__ as ahver
except ImportError:
ahver = "unknown"
epoch = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
timestamp = datetime.datetime.utcfromtimestamp(epoch)
major, minor, bugfix = _version_split(version)
if packagename.lower() == 'astropy':
packagetitle = 'Astropy'
else:
packagetitle = 'Astropy-affiliated package ' + packagename
header = ''
if uses_git:
header = _generate_git_header(packagename, version, githash)
elif not githash:
# _generate_git_header will already generate a new git has for us, but
# for creating a new version.py for a release (even if uses_git=False)
# we still need to get the githash to include in the version.py
# See https://github.com/astropy/astropy-helpers/issues/141
githash = git_helpers.get_git_devstr(sha=True, show_warning=True)
if not header: # If _generate_git_header fails it returns an empty string
header = _FROZEN_VERSION_PY_STATIC_HEADER.format(verstr=version,
githash=githash)
return _FROZEN_VERSION_PY_TEMPLATE.format(packagetitle=packagetitle,
timestamp=timestamp,
header=header,
major=major,
minor=minor,
bugfix=bugfix,
ahver=ahver,
rel=release, debug=debug)
def _generate_git_header(packagename, version, githash):
"""
Generates a header to the version.py module that includes utilities for
probing the git repository for updates (to the current git hash, etc.)
These utilities should only be available in development versions, and not
in release builds.
If this fails for any reason an empty string is returned.
"""
loader = pkgutil.get_loader(git_helpers)
source = loader.get_source(git_helpers.__name__) or ''
source_lines = source.splitlines()
if not source_lines:
log.warn('Cannot get source code for astropy_helpers.git_helpers; '
'git support disabled.')
return ''
idx = 0
for idx, line in enumerate(source_lines):
if line.startswith('# BEGIN'):
break
git_helpers_py = '\n'.join(source_lines[idx + 1:])
verstr = version
new_githash = git_helpers.get_git_devstr(sha=True, show_warning=False)
if new_githash:
githash = new_githash
return _FROZEN_VERSION_PY_WITH_GIT_HEADER.format(
git_helpers=git_helpers_py, packagename=packagename,
verstr=verstr, githash=githash)
def generate_version_py(packagename, version, release=None, debug=None,
uses_git=True, srcdir='.'):
"""Regenerate the version.py module if necessary."""
try:
version_module = get_pkg_version_module(packagename)
try:
last_generated_version = version_module._last_generated_version
except AttributeError:
last_generated_version = version_module.version
try:
last_githash = version_module._last_githash
except AttributeError:
last_githash = version_module.githash
current_release = version_module.release
current_debug = version_module.debug
except ImportError:
version_module = None
last_generated_version = None
last_githash = None
current_release = None
current_debug = None
if release is None:
# Keep whatever the current value is, if it exists
release = bool(current_release)
if debug is None:
# Likewise, keep whatever the current value is, if it exists
debug = bool(current_debug)
package_srcdir = os.path.join(srcdir, *packagename.split('.'))
version_py = os.path.join(package_srcdir, 'version.py')
if (last_generated_version != version or current_release != release or
current_debug != debug):
if '-q' not in sys.argv and '--quiet' not in sys.argv:
log.set_threshold(log.INFO)
if is_distutils_display_option():
# Always silence unnecessary log messages when display options are
# being used
log.set_threshold(log.WARN)
log.info('Freezing version number to {0}'.format(version_py))
with open(version_py, 'w') as f:
# This overwrites the actual version.py
f.write(_get_version_py_str(packagename, version, last_githash,
release, debug, uses_git=uses_git))
invalidate_caches()
if version_module:
imp.reload(version_module)
def get_pkg_version_module(packagename, fromlist=None):
"""Returns the package's .version module generated by
`astropy_helpers.version_helpers.generate_version_py`. Raises an
ImportError if the version module is not found.
If ``fromlist`` is an iterable, return a tuple of the members of the
version module corresponding to the member names given in ``fromlist``.
Raises an `AttributeError` if any of these module members are not found.
"""
if not fromlist:
# Due to a historical quirk of Python's import implementation,
# __import__ will not return submodules of a package if 'fromlist' is
# empty.
# TODO: For Python 3.1 and up it may be preferable to use importlib
# instead of the __import__ builtin
return __import__(packagename + '.version', fromlist=[''])
else:
mod = __import__(packagename + '.version', fromlist=fromlist)
return tuple(getattr(mod, member) for member in fromlist)
# Autogenerated by Astropy-affiliated package astropy_helpers's setup.py on 2019-02-17 11:49:47 UTC
from __future__ import unicode_literals
import datetime
version = "3.1"
githash = "9f82aac6c2141b425e2d639560f7260189d90b54"
major = 3
minor = 1
bugfix = 0
version_info = (major, minor, bugfix)
release = True
timestamp = datetime.datetime(2019, 2, 17, 11, 49, 47)
debug = False
astropy_helpers_version = ""
try:
from ._compiler import compiler
except ImportError:
compiler = "unknown"
try:
from .cython_version import cython_version
except ImportError:
cython_version = "unknown"
astropy-helpers Changelog
*************************
3.1 (2018-12-04)
----------------
- Added extensive documentation about astropy-helpers to the README.rst file. [#416]
- Fixed the compatibility of the build_docs command with Sphinx 1.8 and above. [#413]
- Removing deprecated test_helpers.py file. [#369]
- Removing ez_setup.py file and requiring setuptools 1.0 or later. [#384]
- Remove all sphinx components from ``astropy-helpers``. These are now replaced
by the ``sphinx-astropy`` package in conjunction with the ``astropy-theme-sphinx``,
``sphinx-automodapi``, and ``numpydoc`` packages. [#368]
- openmp_helpers.py: Make add_openmp_flags_if_available() work for clang.
The necessary include, library, and runtime paths now get added to the C test code
used to determine if openmp works.
Autogenerator utility added ``openmp_enabled.is_openmp_enabled()``
which can be called post build to determine state of OpenMP support.
[#382]
- Add version_info tuple to autogenerated version.py. Allows for simple
version checking, i.e. version_info > (2,0,1). [#385]
3.0.2 (2018-06-01)
------------------
- Nothing changed.
3.0.1 (2018-02-22)
------------------
- Nothing changed.
3.0 (2018-02-09)
----------------
- Removing Python 2 support, including 2to3. Packages wishing to keep Python
2 support should NOT update to this version. [#340]
- Removing deprecated _test_compat making astropy a hard dependency for
packages wishing to use the astropy tests machinery. [#314]
- Removing unused 'register' command since packages should be uploaded
with twine and get registered automatically. [#332]
2.0.8 (2018-12-04)
------------------
- Fixed compatibility with Sphinx 1.8+. [#428]
- Fixed error that occurs when installing a package in an environment where
``numpy`` is not already installed. [#404]
- Updated bundled version of sphinx-automodapi to v0.9. [#422]
- Updated bundled version of numpydoc to v0.8.0. [#423]
2.0.7 (2018-06-01)
------------------
- Removing ez_setup.py file and requiring setuptools 1.0 or later. [#384]
2.0.6 (2018-02-24)
------------------
- Avoid deprecation warning due to ``exclude=`` keyword in ``setup.py``. [#379]
2.0.5 (2018-02-22)
------------------
- Fix segmentation faults that occurred when the astropy-helpers submodule
was first initialized in packages that also contained Cython code. [#375]
2.0.4 (2018-02-09)
------------------
- Support dotted package names as namespace packages in generate_version_py.
[#370]
- Fix compatibility with setuptools 36.x and above. [#372]
- Fix false negative in add_openmp_flags_if_available when measuring code
coverage with gcc. [#374]
2.0.3 (2018-01-20)
------------------
- Make sure that astropy-helpers 3.x.x is not downloaded on Python 2. [#362, #363]
- The bundled version of sphinx-automodapi has been updated to v0.7. [#365]
- Add --auto-use and --no-auto-use command-line flags to match the
``auto_use`` configuration option, and add an alias
``--use-system-astropy-helpers`` for ``--no-auto-use``. [#366]
2.0.2 (2017-10-13)
------------------
- Added new helper function add_openmp_flags_if_available that can add
OpenMP compilation flags to a C/Cython extension if needed. [#346]
- Update numpydoc to v0.7. [#343]
- The function ``get_git_devstr`` now returns ``'0'`` instead of ``None`` when
no git repository is present. This allows generation of development version
strings that are in a format that ``setuptools`` expects (e.g. "1.1.3.dev0"
instead of "1.1.3.dev"). [#330]
- It is now possible to override generated timestamps to make builds
reproducible by setting the ``SOURCE_DATE_EPOCH`` environment variable [#341]
- Mark Sphinx extensions as parallel-safe. [#344]
- Switch to using mathjax instead of imgmath for local builds. [#342]
- Deprecate ``exclude`` parameter of various functions in setup_helpers since
it could not work as intended. Add new function ``add_exclude_packages`` to
provide intended behavior. [#331]
- Allow custom Sphinx doctest extension to recognize and process standard
doctest directives ``testsetup`` and ``doctest``. [#335]
2.0.1 (2017-07-28)
------------------
- Fix compatibility with Sphinx <1.5. [#326]
2.0 (2017-07-06)
----------------
- Add support for package that lies in a subdirectory. [#249]
- Removing ``compat.subprocess``. [#298]
- Python 3.3 is no longer supported. [#300]
- The 'automodapi' Sphinx extension (and associated dependencies) has now
been moved to a standalone package which can be found at
https://github.com/astropy/sphinx-automodapi - this is now bundled in
astropy-helpers under astropy_helpers.extern.automodapi for
convenience. Version shipped with astropy-helpers is v0.6.
[#278, #303, #309, #323]
- The ``numpydoc`` Sphinx extension has now been moved to
``astropy_helpers.extern``. [#278]
- Fix ``build_docs`` error catching, so it doesn't hide Sphinx errors. [#292]
- Fix compatibility with Sphinx 1.6. [#318]
- Updating ez_setup.py to the last version before it's removal. [#321]
1.3.1 (2017-03-18)
------------------
- Fixed the missing button to hide output in documentation code
blocks. [#287]
- Fixed bug when ``build_docs`` when running with the clean (-l) option. [#289]
- Add alternative location for various intersphinx inventories to fall back
to. [#293]
1.3 (2016-12-16)
----------------
- ``build_sphinx`` has been deprecated in favor of the ``build_docs`` command.
[#246]
- Force the use of Cython's old ``build_ext`` command. A new ``build_ext``
command was added in Cython 0.25, but it does not work with astropy-helpers
currently. [#261]
1.2 (2016-06-18)
----------------
- Added sphinx configuration value ``automodsumm_inherited_members``.
If ``True`` this will include members that are inherited from a base
class in the generated API docs. Defaults to ``False`` which matches
the previous behavior. [#215]
- Fixed ``build_sphinx`` to recognize builds that succeeded but have output
*after* the "build succeeded." statement. This only applies when
``--warnings-returncode`` is given (which is primarily relevant for Travis
documentation builds). [#223]
- Fixed ``build_sphinx`` the sphinx extensions to not output a spurious warning
for sphinx versions > 1.4. [#229]
- Add Python version dependent local sphinx inventories that contain
otherwise missing references. [#216]
- ``astropy_helpers`` now require Sphinx 1.3 or later. [#226]
1.1.2 (2016-03-9)
-----------------
- The CSS for the sphinx documentation was altered to prevent some text overflow
problems. [#217]
1.1.1 (2015-12-23)
------------------
- Fixed crash in build with ``AttributeError: cython_create_listing`` with
older versions of setuptools. [#209, #210]
1.1 (2015-12-10)
----------------
- The original ``AstropyTest`` class in ``astropy_helpers``, which implements
the ``setup.py test`` command, is deprecated in favor of moving the
implementation of that command closer to the actual Astropy test runner in
``astropy.tests``. Now a dummy ``test`` command is provided solely for
informing users that they need ``astropy`` installed to run the tests
(however, the previous, now deprecated implementation is still provided and
continues to work with older versions of Astropy). See the related issue for
more details. [#184]
- Added a useful new utility function to ``astropy_helpers.utils`` called
``find_data_files``. This is similar to the ``find_packages`` function in
setuptools in that it can be used to search a package for data files
(matching a pattern) that can be passed to the ``package_data`` argument for
``setup()``. See the docstring to ``astropy_helpers.utils.find_data_files``
for more details. [#42]
- The ``astropy_helpers`` module now sets the global ``_ASTROPY_SETUP_``
flag upon import (from within a ``setup.py``) script, so it's not necessary
to have this in the ``setup.py`` script explicitly. If in doubt though,
there's no harm in setting it twice. Putting it in ``astropy_helpers``
just ensures that any other imports that occur during build will have this
flag set. [#191]
- It is now possible to use Cython as a ``setup_requires`` build requirement,
and still build Cython extensions even if Cython wasn't available at the
beginning of the build processes (that is, is automatically downloaded via
setuptools' processing of ``setup_requires``). [#185]
- Moves the ``adjust_compiler`` check into the ``build_ext`` command itself,
so it's only used when actually building extension modules. This also
deprecates the stand-alone ``adjust_compiler`` function. [#76]
- When running the ``build_sphinx`` / ``build_docs`` command with the ``-w``
option, the output from Sphinx is streamed as it runs instead of silently
buffering until the doc build is complete. [#197]
1.0.7 (unreleased)
------------------
- Fix missing import in ``astropy_helpers/utils.py``. [#196]
1.0.6 (2015-12-04)
------------------
- Fixed bug where running ``./setup.py build_sphinx`` could return successfully
even when the build was not successful (and should have returned a non-zero
error code). [#199]
1.0.5 (2015-10-02)
------------------
- Fixed a regression in the ``./setup.py test`` command that was introduced in
v1.0.4.
1.0.4 (2015-10-02)
------------------
- Fixed issue with the sphinx documentation css where the line numbers for code
blocks were not aligned with the code. [#179, #180]
- Fixed crash that could occur when trying to build Cython extension modules
when Cython isn't installed. Normally this still results in a failed build,
but was supposed to provide a useful error message rather than crash
outright (this was a regression introduced in v1.0.3). [#181]
- Fixed a crash that could occur on Python 3 when a working C compiler isn't
found. [#182]
- Quieted warnings about deprecated Numpy API in Cython extensions, when
building Cython extensions against Numpy >= 1.7. [#183, #186]
- Improved support for py.test >= 2.7--running the ``./setup.py test`` command
now copies all doc pages into the temporary test directory as well, so that
all test files have a "common root directory". [#189, #190]
1.0.3 (2015-07-22)
------------------
- Added workaround for sphinx-doc/sphinx#1843, a but in Sphinx which
prevented descriptor classes with a custom metaclass from being documented
correctly. [#158]
- Added an alias for the ``./setup.py build_sphinx`` command as
``./setup.py build_docs`` which, to a new contributor, should hopefully be
less cryptic. [#161]
- The fonts in graphviz diagrams now match the font of the HTML content. [#169]
- When the documentation is built on readthedocs.org, MathJax will be
used for math rendering. When built elsewhere, the "pngmath"
extension is still used for math rendering. [#170]
- Fix crash when importing astropy_helpers when running with ``python -OO``
[#171]
- The ``build`` and ``build_ext`` stages now correctly recognize the presence
of C++ files in Cython extensions (previously only vanilla C worked). [#173]
1.0.2 (2015-04-02)
------------------
- Various fixes enabling the astropy-helpers Sphinx build command and
Sphinx extensions to work with Sphinx 1.3. [#148]
- More improvement to the ability to handle multiple versions of
astropy-helpers being imported in the same Python interpreter session
in the (somewhat rare) case of nested installs. [#147]
- To better support high resolution displays, use SVG for the astropy
logo and linkout image, falling back to PNGs for browsers that
support it. [#150, #151]
- Improve ``setup_helpers.get_compiler_version`` to work with more compilers,
and to return more info. This will help fix builds of Astropy on less
common compilers, like Sun C. [#153]
1.0.1 (2015-03-04)
------------------
- Released in concert with v0.4.8 to address the same issues.
0.4.8 (2015-03-04)
------------------
- Improved the ``ah_bootstrap`` script's ability to override existing
installations of astropy-helpers with new versions in the context of
installing multiple packages simultaneously within the same Python
interpreter (e.g. when one package has in its ``setup_requires`` another
package that uses a different version of astropy-helpers. [#144]
- Added a workaround to an issue in matplotlib that can, in rare cases, lead
to a crash when installing packages that import matplotlib at build time.
[#144]
1.0 (2015-02-17)
----------------
- Added new pre-/post-command hook points for ``setup.py`` commands. Now any
package can define code to run before and/or after any ``setup.py`` command
without having to manually subclass that command by adding
``pre_<command_name>_hook`` and ``post_<command_name>_hook`` callables to
the package's ``setup_package.py`` module. See the PR for more details.
[#112]
- The following objects in the ``astropy_helpers.setup_helpers`` module have
been relocated:
- ``get_dummy_distribution``, ``get_distutils_*``, ``get_compiler_option``,
``add_command_option``, ``is_distutils_display_option`` ->
``astropy_helpers.distutils_helpers``
- ``should_build_with_cython``, ``generate_build_ext_command`` ->
``astropy_helpers.commands.build_ext``
- ``AstropyBuildPy`` -> ``astropy_helpers.commands.build_py``
- ``AstropyBuildSphinx`` -> ``astropy_helpers.commands.build_sphinx``
- ``AstropyInstall`` -> ``astropy_helpers.commands.install``
- ``AstropyInstallLib`` -> ``astropy_helpers.commands.install_lib``
- ``AstropyRegister`` -> ``astropy_helpers.commands.register``
- ``get_pkg_version_module`` -> ``astropy_helpers.version_helpers``
- ``write_if_different``, ``import_file``, ``get_numpy_include_path`` ->
``astropy_helpers.utils``
All of these are "soft" deprecations in the sense that they are still
importable from ``astropy_helpers.setup_helpers`` for now, and there is
no (easy) way to produce deprecation warnings when importing these objects
from ``setup_helpers`` rather than directly from the modules they are
defined in. But please consider updating any imports to these objects.
[#110]
- Use of the ``astropy.sphinx.ext.astropyautosummary`` extension is deprecated
for use with Sphinx < 1.2. Instead it should suffice to remove this
extension for the ``extensions`` list in your ``conf.py`` and add the stock
``sphinx.ext.autosummary`` instead. [#131]
0.4.7 (2015-02-17)
------------------
- Fixed incorrect/missing git hash being added to the generated ``version.py``
when creating a release. [#141]
0.4.6 (2015-02-16)
------------------
- Fixed problems related to the automatically generated _compiler
module not being created properly. [#139]
0.4.5 (2015-02-11)
------------------
- Fixed an issue where ah_bootstrap.py could blow up when astropy_helper's
version number is 1.0.
- Added a workaround for documentation of properties in the rare case
where the class's metaclass has a property of the same name. [#130]
- Fixed an issue on Python 3 where importing a package using astropy-helper's
generated version.py module would crash when the current working directory
is an empty git repository. [#114, #137]
- Fixed an issue where the "revision count" appended to .dev versions by
the generated version.py did not accurately reflect the revision count for
the package it belongs to, and could be invalid if the current working
directory is an unrelated git repository. [#107, #137]
- Likewise, fixed a confusing warning message that could occur in the same
circumstances as the above issue. [#121, #137]
0.4.4 (2014-12-31)
------------------
- More improvements for building the documentation using Python 3.x. [#100]
- Additional minor fixes to Python 3 support. [#115]
- Updates to support new test features in Astropy [#92, #106]
0.4.3 (2014-10-22)
------------------
- The generated ``version.py`` file now preserves the git hash of installed
copies of the package as well as when building a source distribution. That
is, the git hash of the changeset that was installed/released is preserved.
[#87]
- In smart resolver add resolution for class links when they exist in the
intersphinx inventory, but not the mapping of the current package
(e.g. when an affiliated package uses an astropy core class of which
"actual" and "documented" location differs) [#88]
- Fixed a bug that could occur when running ``setup.py`` for the first time
in a repository that uses astropy-helpers as a submodule:
``AttributeError: 'NoneType' object has no attribute 'mkdtemp'`` [#89]
- Fixed a bug where optional arguments to the ``doctest-skip`` Sphinx
directive were sometimes being left in the generated documentation output.
[#90]
- Improved support for building the documentation using Python 3.x. [#96]
- Avoid error message if .git directory is not present. [#91]
0.4.2 (2014-08-09)
------------------
- Fixed some CSS issues in generated API docs. [#69]
- Fixed the warning message that could be displayed when generating a
version number with some older versions of git. [#77]
- Fixed automodsumm to work with new versions of Sphinx (>= 1.2.2). [#80]
0.4.1 (2014-08-08)
------------------
- Fixed git revision count on systems with git versions older than v1.7.2.
[#70]
- Fixed display of warning text when running a git command fails (previously
the output of stderr was not being decoded properly). [#70]
- The ``--offline`` flag to ``setup.py`` understood by ``ah_bootstrap.py``
now also prevents git from going online to fetch submodule updates. [#67]
- The Sphinx extension for converting issue numbers to links in the changelog
now supports working on arbitrary pages via a new ``conf.py`` setting:
``changelog_links_docpattern``. By default it affects the ``changelog``
and ``whatsnew`` pages in one's Sphinx docs. [#61]
- Fixed crash that could result from users with missing/misconfigured
locale settings. [#58]
- The font used for code examples in the docs is now the
system-defined ``monospace`` font, rather than ``Minaco``, which is
not available on all platforms. [#50]
0.4 (2014-07-15)
----------------
- Initial release of astropy-helpers. See `APE4
<https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_ for
details of the motivation and design of this package.
- The ``astropy_helpers`` package replaces the following modules in the
``astropy`` package:
- ``astropy.setup_helpers`` -> ``astropy_helpers.setup_helpers``
- ``astropy.version_helpers`` -> ``astropy_helpers.version_helpers``
- ``astropy.sphinx`` - > ``astropy_helpers.sphinx``
These modules should be considered deprecated in ``astropy``, and any new,
non-critical changes to those modules will be made in ``astropy_helpers``
instead. Affiliated packages wishing to make use those modules (as in the
Astropy package-template) should use the versions from ``astropy_helpers``
instead, and include the ``ah_bootstrap.py`` script in their project, for
bootstrapping the ``astropy_helpers`` package in their setup.py script.
Contributing to astropy-helpers
===============================
The guidelines for contributing to ``astropy-helpers`` are generally the same
as the [contributing guidelines for the astropy core
package](http://github.com/astropy/astropy/blob/master/CONTRIBUTING.md).
Basically, report relevant issues in the ``astropy-helpers`` issue tracker, and
we welcome pull requests that broadly follow the [Astropy coding
guidelines](http://docs.astropy.org/en/latest/development/codeguide.html).
The key subtlety lies in understanding the relationship between ``astropy`` and
``astropy-helpers``. This package contains the build, installation, and
documentation tools used by astropy. It also includes support for the
``setup.py test`` command, though Astropy is still required for this to
function (it does not currently include the full Astropy test runner). So
issues or improvements to that functionality should be addressed in this
package. Any other aspect of the [astropy core
package](http://github.com/astropy/astropy) (or any other package that uses
``astropy-helpers``) should be addressed in the github repository for that
package.
Copyright (c) 2014, Astropy Developers
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 Astropy Team 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.
# The OpenMP helpers include code heavily adapted from astroscrappy, released
# under the following license:
#
# Copyright (c) 2015, Curtis McCully
# 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 Astropy Team 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.
include README.rst
include CHANGES.rst
include LICENSE.rst
recursive-include licenses *
include ah_bootstrap.py
exclude *.pyc *.o
prune build
prune astropy_helpers/tests
astropy-helpers
===============
.. image:: https://travis-ci.org/astropy/astropy-helpers.svg
:target: https://travis-ci.org/astropy/astropy-helpers
.. image:: https://ci.appveyor.com/api/projects/status/rt9161t9mhx02xp7/branch/master?svg=true
:target: https://ci.appveyor.com/project/Astropy/astropy-helpers
.. image:: https://codecov.io/gh/astropy/astropy-helpers/branch/master/graph/badge.svg
:target: https://codecov.io/gh/astropy/astropy-helpers
**Warning:** Please note that version ``v3.0`` and later of ``astropy-helpers``
requires Python 3.5 or later. If you wish to maintain Python 2 support
for your package that uses ``astropy-helpers``, then do not upgrade the
helpers to ``v3.0+``. We will still provide Python 2.7 compatible
releases on the ``v2.0.x`` branch during the lifetime of the ``astropy``
core package LTS of ``v2.0.x``.
About
-----
This project provides a Python package, ``astropy_helpers``, which includes
many build, installation, and documentation-related tools used by the Astropy
project, but packaged separately for use by other projects that wish to
leverage this work. The motivation behind this package and details of its
implementation are in the accepted
`Astropy Proposal for Enhancement (APE) 4 <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_.
``astropy_helpers`` includes a special "bootstrap" module called
``ah_bootstrap.py`` which is intended to be used by a project's setup.py in
order to ensure that the ``astropy_helpers`` package is available for
build/installation.
As described in `APE4 <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_, the version
numbers for ``astropy_helpers`` follow the corresponding major/minor version of
the `astropy core package <http://www.astropy.org/>`_, but with an independent
sequence of micro (bugfix) version numbers. Hence, the initial release is 0.4,
in parallel with Astropy v0.4, which will be the first version of Astropy to
use ``astropy-helpers``.
For examples of how to implement ``astropy-helpers`` in a project,
see the ``setup.py`` and ``setup.cfg`` files of the
`Affiliated package template <https://github.com/astropy/package-template>`_.
What does astropy-helpers provide?
----------------------------------
Astropy-helpers' big-picture purpose is to provide customization to Python's
packaging infrastructure process in ways that the Astropy Project has found to
help simplifying the developint and releasing packages. This is primarily
build around ``setup.py`` commands, as outlined below, as well as code to help
manage version numbers and better control the build process of larger packages.
Custom setup.py commands
^^^^^^^^^^^^^^^^^^^^^^^^
The main part of astropy-helpers is to provide customized setuptools commands.
For example, in a package that uses astropy-helpers, the following command
will be available::
python setup.py build_docs
and this command is implemented in astropy-helpers. To use the custom
commands described here, add::
from astropy_helpers.setup_helpers import register_commands
to your ``setup.py`` file, then do::
cmdclassd = register_commands(NAME, VERSION, RELEASE)
where ``NAME`` is the name of your package, ``VERSION`` is the full version
string, and ``RELEASE`` is a boolean value indicating whether the version is
a stable released version (``True``) or a developer version (``False``).
Finally, pass ``cmdclassd`` to the ``setup`` function::
setup(...,
cmdclass=cmdclassd)
The commands we provide or customize are:
**python setup.py test**
This command will automatically build the package, install it to a temporary
directory, and run the tests using `pytest <http://pytest.org/>`_ on this
installed version. Note that the bulk of this command is actually defined
in ``astropy.tests.command.AstropyTest``, because that allows the test
machinery to operate outside a setuptools command. This, here we
simply define the custom
setuptools command.
**python setup.py sdist**
We redefine ``sdist`` to use the version from distutils rather than from
setuptools, as the setuptools version requires duplication of information
in ``MANIFEST.in``.
**python setup.py build_docs**
This command will automatically build the package, then run sphinx to build
the documentation. This makes development much easier because it ensures
sphinx extensions that use the package's code to make documentation are
actually using the in-development version of the code. Sphinx itself
provides a custom setuptools command, which we
expand with the following options:
* ``-w``: set the return code to 1 if there are any warnings during the build
process.
* ``-l``: completely clean previous builds, including files generated by
the sphinx-automodapi package (which creates API pages for different
functions/classes).
* ``-n``: disable the intersphinx option.
* ``-o``: open the documentation in a browser if a build finishes successfully.
In addition, ``build_docs`` will automatically download and temporarily install
sphinx-astropy (which is a meta-package that
provides standardized configuration and documentation dependencies for astropy
packages) if it isn't already installed. Temporary installation means that the
package will be installed into an ``.eggs`` directory in the current working
directory, and it will only be available for the duration of the call to
``build_docs``.
**python setup.py build_ext**
This is also used when running ``build`` or ``install``. We add several features
compared to the default ``build_ext`` command:
* For packages with C/Cython extensions, we create a ``packagename._compiler``
submodule that contains information about the compilers used.
* Packages that need to build C extensions using the Numpy C API, we allow
those packages to define the include path as ``'numpy'`` as opposed to having
to import Numpy and call ``get_include``. The goal is to solve the issue that
if one has to import Numpy to define extensions, then Numpy has to be
installed/available before the package is installed, which means that one
needs to install Numpy in a separate installation step.
* We detect broken compilers and replace them with other compilers on-the-fly
unless the compiler is explicitly specified with the ``CC`` environment
variable.
* If Cython is not installed, then we automatically check for generated C files
(which are normally present in the stable releases) and give a nice error
if these are not found.
Version helpers
^^^^^^^^^^^^^^^^
Another piece of functionality we provide in astropy-helpers is the ability
to generate a ``packagename.version`` file that includes functions that
automatically set the version string for developer versions, to e.g.
``3.2.dev22213`` so that each developer version has a unique number (although
note that branches an equal number of commits away from the master branch will
share the same version number). To use this, import::
from astropy_helpers.git_helpers import get_git_devstr
in your ``setup.py`` file, and you will then be able to use::
VERSION += get_git_devstr()
where ``VERSION`` is a version string without any developer version suffix.
We then also provide a function that generates a ``version.py`` file inside your
package (which can then be imported as ``packagename.version``) that contains
variables such as ``major``, ``minor``, and ``bugfix``, as well as
``version_info`` (a tuple of the previous three values), a ``release`` flag that
indicates whether we are using a stable release, and several other complementary
variables. To use this, import::
from astropy_helpers.version_helpers import generate_version_py
in your ``setup.py`` file, and call::
generate_version_py(NAME, VERSION, RELEASE, uses_git=not RELEASE)
where ``NAME`` is the name of your package, ``VERSION`` is the full version string
(including any developer suffix), ``RELEASE`` indicates whether the version is a
stable or developer version, and ``uses_git`` indicates whether we are in a git
repository (using ``not RELEASE`` is sensible since git is not available in a
stable release).
Collecting package information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``setup`` function from setuptools can take a number of options that indicate
for example what extensions to build, and what package data to include. However,
for large packages this can become cumbersome. We therefore provide a mechanism
for defining extensions and package data inside individual sub-packages. To do
this, you can create ``setup_package.py`` files anywhere in your package, and
these files can include one or more of the following functions:
* ``get_package_data``:
This function, if defined, should return a dictionary mapping the name of
the subpackage(s) that need package data to a list of data file paths
(possibly including wildcards) relative to the path of the package's source
code. e.g. if the source distribution has a needed data file
``astropy/wcs/tests/data/3d_cd.hdr``, this function should return
``{'astropy.wcs.tests':['data/3d_cd.hdr']}``. See the ``package_data``
option of the :func:`distutils.core.setup` function.
It is recommended that all such data be in a directory named ``data`` inside
the package within which it is supposed to be used. This package data
should be accessed via the ``astropy.utils.data.get_pkg_data_filename`` and
``astropy.utils.data.get_pkg_data_fileobj`` functions.
* ``get_extensions``:
This provides information for building C or Cython extensions. If defined,
it should return a list of ``distutils.core.Extension`` objects.
* ``get_build_options``:
This function allows a package to add extra build options. It
should return a list of tuples, where each element has:
- *name*: The name of the option as it would appear on the
commandline or in the ``setup.cfg`` file.
- *doc*: A short doc string for the option, displayed by
``setup.py build --help``.
- *is_bool* (optional): When `True`, the option is a boolean
option and doesn't have an associated value.
Once an option has been added, its value can be looked up using
``astropy_helpers.setup_helpers.get_distutils_build_option``.
* ``get_external_libraries``:
This function declares that the package uses libraries that are
included in the astropy distribution that may also be distributed
elsewhere on the users system. It should return a list of library
names. For each library, a new build option is created,
``'--use-system-X'`` which allows the user to request to use the
system's copy of the library. The package would typically call
``astropy_helpers.setup_helpers.use_system_library`` from its
``get_extensions`` function to determine if the package should use
the system library or the included one.
* ``get_entry_points()``:
This function can returns a dict formatted as required by
the ``entry_points`` argument to ``setup()``.
With these files in place, you can then add the following to your ``setup.py``
file::
from astropy_helpers.setup_helpers import get_package_info
...
package_info = get_package_info()
...
setup(..., **package_info)
OpenMP helpers
^^^^^^^^^^^^^^
We provide a helper function ``add_openmp_flags_if_available`` that can be used
to automatically add OpenMP flags for C/Cython extensions, based on whether
OpenMP is available and produces executable code. To use this, edit the
``setup_package.py`` file where you define a C extension, import the helper
function::
from astropy_helpers.openmp_helpers import add_openmp_flags_if_available
then once you have defined the extension and before returning it, use it as::
extension = Extension(...)
add_openmp_flags_if_available(extension)
return [extension]
Using astropy-helpers
---------------------
The easiest way to get set up with astropy-helpers in a new package is to use
the `package-template <http://docs.astropy.org/projects/package-template>`_
that we provide. This template is specifically designed for use with the helpers,
so using it avoids some of the tedium of setting up the heleprs.
However, we now go through the steps of adding astropy-helpers
as a submodule to a package in case you wish to do so manually. First, add
astropy-helpers as a submodule at the root of your repository::
git submodule add git://github.com/astropy/astropy-helpers astropy_helpers
Then go inside the submodule and check out a stable version of astropy-helpers.
You can see the available versions by running::
$ cd astropy_helpers
$ git tag
...
v2.0.6
v2.0.7
...
v3.0.1
v3.0.2
If you want to support Python 2, pick the latest v2.0.x version (in the above
case ``v2.0.7``) and if you don't need to support Python 2, just pick the latest
stable version (in the above case ``v3.0.2``). Check out this version with e.g.::
$ git checkout v3.0.2
Then go back up to the root of your repository and copy the ``ah_bootstrap.py``
file from the submodule to the root of your repository::
$ cd ..
$ cp astropy_helpers/ah_bootstrap.py .
Finally, add::
import ah_bootstrap
at the top of your ``setup.py`` file. This will ensure that ``astropy_helpers``
is now available to use in your ``setup.py`` file. Finally, add then commit your
changes::
git add astropy_helpers ah_bootstrap.py setup.py
git commit -m "Added astropy-helpers"
Updating astropy-helpers
------------------------
If you would like the Astropy team to automatically open pull requests to update
astropy-helpers in your package, then see the instructions `here
<https://github.com/astropy/astropy-procedures/blob/master/update-packages/README.md>`_.
To instead update astropy-helpers manually, go inside the submodule and do::
cd astropy_helpers
git fetch origin
Then checkout the version you want to use, e.g.::
git checkout v3.0.3
Go back up to the root of the repository and update the ``ah_bootstap.py`` file
too, then add your changes::
cp astropy_helpers/ah_bootstrap.py .
git add astropy_helpers ah_bootstrap.py
git commit ...
[tool:pytest]
norecursedirs =
.tox
astropy_helpers/tests/package_template
python_functions = test_
#!/usr/bin/env python
# Licensed under a 3-clause BSD style license - see LICENSE.rst
import ah_bootstrap
import pkg_resources
from setuptools import setup
from astropy_helpers.setup_helpers import (register_commands, get_package_info,
add_exclude_packages)
from astropy_helpers.version_helpers import generate_version_py
NAME = 'astropy_helpers'
VERSION = '3.1'
RELEASE = 'dev' not in VERSION
generate_version_py(NAME, VERSION, RELEASE, False, uses_git=not RELEASE)
# Use the updated version including the git rev count
from astropy_helpers.version import version as VERSION
add_exclude_packages(['astropy_helpers.tests'])
cmdclass = register_commands(NAME, VERSION, RELEASE)
# This package actually doesn't use the Astropy test command
del cmdclass['test']
setup(
name=pkg_resources.safe_name(NAME), # astropy_helpers -> astropy-helpers
version=VERSION,
description='Utilities for building and installing Astropy, Astropy '
'affiliated packages, and their respective documentation.',
author='The Astropy Developers',
author_email='astropy.team@gmail.com',
license='BSD',
url=' https://github.com/astropy/astropy-helpers',
long_description=open('README.rst').read(),
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Framework :: Setuptools Plugin',
'Framework :: Sphinx :: Extension',
'Framework :: Sphinx :: Theme',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Topic :: Software Development :: Build Tools',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Archiving :: Packaging'
],
cmdclass=cmdclass,
python_requires='>=3.5',
zip_safe=False,
**get_package_info()
)

Sorry, the diff of this file is too big to display