[Git][debian-gis-team/pyorbital][upstream] New upstream version 1.4.0
Antonio Valentino
gitlab at salsa.debian.org
Sun Nov 11 10:27:32 GMT 2018
Antonio Valentino pushed to branch upstream at Debian GIS Project / pyorbital
Commits:
8c3639b1 by Antonio Valentino at 2018-11-11T08:29:36Z
New upstream version 1.4.0
- - - - -
21 changed files:
- − .bumpversion.cfg
- + .gitattributes
- − .gitchangelog.rc
- .github/PULL_REQUEST_TEMPLATE.md
- .travis.yml
- + CHANGELOG.md
- MANIFEST.in
- + RELEASING.md
- doc/source/conf.py
- doc/source/index.rst
- pyorbital/__init__.py
- pyorbital/geoloc_instrument_definitions.py
- pyorbital/orbital.py
- pyorbital/tests/test_aiaa.py
- pyorbital/tests/test_geoloc.py
- pyorbital/tests/test_orbital.py
- pyorbital/tlefile.py
- pyorbital/version.py
- setup.cfg
- setup.py
- + versioneer.py
Changes:
=====================================
.bumpversion.cfg deleted
=====================================
@@ -1,7 +0,0 @@
-[bumpversion]
-current_version = 1.3.1
-commit = True
-tag = True
-
-[bumpversion:file:pyorbital/version.py]
-
=====================================
.gitattributes
=====================================
@@ -0,0 +1 @@
+pyorbital/version.py export-subst
=====================================
.gitchangelog.rc deleted
=====================================
@@ -1,190 +0,0 @@
-##
-## Format
-##
-## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...]
-##
-## Description
-##
-## ACTION is one of 'chg', 'fix', 'new'
-##
-## Is WHAT the change is about.
-##
-## 'chg' is for refactor, small improvement, cosmetic changes...
-## 'fix' is for bug fixes
-## 'new' is for new features, big improvement
-##
-## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc'
-##
-## Is WHO is concerned by the change.
-##
-## 'dev' is for developpers (API changes, refactors...)
-## 'usr' is for final users (UI changes)
-## 'pkg' is for packagers (packaging changes)
-## 'test' is for testers (test only related changes)
-## 'doc' is for doc guys (doc only changes)
-##
-## COMMIT_MSG is ... well ... the commit message itself.
-##
-## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic'
-##
-## They are preceded with a '!' or a '@' (prefer the former, as the
-## latter is wrongly interpreted in github.) Commonly used tags are:
-##
-## 'refactor' is obviously for refactoring code only
-## 'minor' is for a very meaningless change (a typo, adding a comment)
-## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...)
-## 'wip' is for partial functionality but complete subfunctionality.
-##
-## Example:
-##
-## new: usr: support of bazaar implemented
-## chg: re-indentend some lines !cosmetic
-## new: dev: updated code to be compatible with last version of killer lib.
-## fix: pkg: updated year of licence coverage.
-## new: test: added a bunch of test around user usability of feature X.
-## fix: typo in spelling my name in comment. !minor
-##
-## Please note that multi-line commit message are supported, and only the
-## first line will be considered as the "summary" of the commit message. So
-## tags, and other rules only applies to the summary. The body of the commit
-## message will be displayed in the changelog without reformatting.
-
-
-##
-## ``ignore_regexps`` is a line of regexps
-##
-## Any commit having its full commit message matching any regexp listed here
-## will be ignored and won't be reported in the changelog.
-##
-ignore_regexps = [
- r'@minor', r'!minor',
- r'@cosmetic', r'!cosmetic',
- r'@refactor', r'!refactor',
- r'@wip', r'!wip',
- r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:',
- r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:',
- r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$',
- ]
-
-
-## ``section_regexps`` is a list of 2-tuples associating a string label and a
-## list of regexp
-##
-## Commit messages will be classified in sections thanks to this. Section
-## titles are the label, and a commit is classified under this section if any
-## of the regexps associated is matching.
-##
-section_regexps = [
- ('New', [
- r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
- ]),
- ('Changes', [
- r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
- ]),
- ('Fix', [
- r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
- ]),
-
- ('Other', None ## Match all lines
- ),
-
-]
-
-
-## ``body_process`` is a callable
-##
-## This callable will be given the original body and result will
-## be used in the changelog.
-##
-## Available constructs are:
-##
-## - any python callable that take one txt argument and return txt argument.
-##
-## - ReSub(pattern, replacement): will apply regexp substitution.
-##
-## - Indent(chars=" "): will indent the text with the prefix
-## Please remember that template engines gets also to modify the text and
-## will usually indent themselves the text if needed.
-##
-## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns
-##
-## - noop: do nothing
-##
-## - ucfirst: ensure the first letter is uppercase.
-## (usually used in the ``subject_process`` pipeline)
-##
-## - final_dot: ensure text finishes with a dot
-## (usually used in the ``subject_process`` pipeline)
-##
-## - strip: remove any spaces before or after the content of the string
-##
-## Additionally, you can `pipe` the provided filters, for instance:
-#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ")
-#body_process = Wrap(regexp=r'\n(?=\w+\s*:)')
-body_process = noop
-
-
-## ``subject_process`` is a callable
-##
-## This callable will be given the original subject and result will
-## be used in the changelog.
-##
-## Available constructs are those listed in ``body_process`` doc.
-subject_process = (strip |
- ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') |
- ucfirst | final_dot)
-
-
-## ``tag_filter_regexp`` is a regexp
-##
-## Tags that will be used for the changelog must match this regexp.
-##
-tag_filter_regexp = r'^v[0-9]+\.[0-9]+(\.[0-9]+)?$'
-
-
-## ``unreleased_version_label`` is a string
-##
-## This label will be used as the changelog Title of the last set of changes
-## between last valid tag and HEAD if any.
-unreleased_version_label = "%%version%% (unreleased)"
-
-
-## ``output_engine`` is a callable
-##
-## This will change the output format of the generated changelog file
-##
-## Available choices are:
-##
-## - rest_py
-##
-## Legacy pure python engine, outputs ReSTructured text.
-## This is the default.
-##
-## - mustache(<template_name>)
-##
-## Template name could be any of the available templates in
-## ``templates/mustache/*.tpl``.
-## Requires python package ``pystache``.
-## Examples:
-## - mustache("markdown")
-## - mustache("restructuredtext")
-##
-## - makotemplate(<template_name>)
-##
-## Template name could be any of the available templates in
-## ``templates/mako/*.tpl``.
-## Requires python package ``mako``.
-## Examples:
-## - makotemplate("restructuredtext")
-##
-output_engine = rest_py
-#output_engine = mustache("restructuredtext")
-#output_engine = mustache("markdown")
-#output_engine = makotemplate("restructuredtext")
-
-
-## ``include_merges`` is a boolean
-##
-## This option tells git-log whether to include merge commits in the log.
-## The default is to include them.
-include_merges = True
=====================================
.github/PULL_REQUEST_TEMPLATE.md
=====================================
@@ -1,9 +1,10 @@
-<!-- Please make the PR against the `develop` branch. -->
+<!-- Please make the PR against the `master` branch. -->
<!-- Describe what your PR does, and why -->
- [ ] Closes #xxxx <!-- remove if there is no corresponding issue, which should only be the case for minor changes -->
- [ ] Tests added <!-- for all bug fixes or enhancements -->
- [ ] Tests passed <!-- for all non-documentation changes) -->
- - [ ] Passes ``git diff origin/develop **/*py | flake8 --diff`` <!-- remove if you did not edit any Python files -->
+ - [ ] Passes ``git diff origin/master **/*py | flake8 --diff`` <!-- remove if you did not edit any Python files -->
- [ ] Fully documented <!-- remove if this change should not be visible to users, e.g., if it is an internal clean-up, or if this is part of a larger project that will be documented later -->
+1
\ No newline at end of file
=====================================
.travis.yml
=====================================
@@ -1,9 +1,9 @@
language: python
python:
- '2.7'
-- "3.5"
- "3.6"
install:
+- pip install dask[array] xarray
- pip install .
- pip install coveralls
script: coverage run --source=pyorbital setup.py test
=====================================
CHANGELOG.md
=====================================
@@ -0,0 +1,26 @@
+## Version 1.4.0 (2018/10/23)
+
+### Issues Closed
+
+* [Issue 36](https://github.com/pytroll/pyorbital/issues/36) - Issue(s) with get_next_passes
+* [Issue 34](https://github.com/pytroll/pyorbital/issues/34) - Get root secant converging to wrong solution ([PR 35](https://github.com/pytroll/pyorbital/pull/35))
+* [Issue 30](https://github.com/pytroll/pyorbital/issues/30) - get_observer_look turns xarray.DataArray objects into dask.array objects
+* [Issue 29](https://github.com/pytroll/pyorbital/issues/29) - URL error
+* [Issue 27](https://github.com/pytroll/pyorbital/issues/27) - satellite.get_lonlatalt(now) returns wrong longitude with numpy < 1.11
+* [Issue 18](https://github.com/pytroll/pyorbital/issues/18) - Sun-satellite angle ranges are not consistent
+
+In this release 6 issues were closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 39](https://github.com/pytroll/pyorbital/pull/39) - Bugfix python3
+* [PR 35](https://github.com/pytroll/pyorbital/pull/35) - Use Scipy brentq method instead of secant method to perform root-finding ([34](https://github.com/pytroll/pyorbital/issues/34))
+
+#### Features added
+
+* [PR 37](https://github.com/pytroll/pyorbital/pull/37) - Switch to versioneer
+* [PR 33](https://github.com/pytroll/pyorbital/pull/33) - Remove Develop branch
+
+In this release 4 pull requests were closed.
=====================================
MANIFEST.in
=====================================
@@ -3,3 +3,5 @@ recursive-include doc/source *
include LICENSE.txt
include MANIFEST.in
include README
+include versioneer.py
+include pyorbital/version.py
=====================================
RELEASING.md
=====================================
@@ -0,0 +1,27 @@
+# Releasing Pyorbital
+
+prerequisites: `pip install setuptools twine`
+
+1. checkout master
+2. pull from repo
+3. run the unittests
+4. run `loghub` and update the `CHANGELOG.md` file:
+
+```
+loghub pytroll/pyorbital -u <username> -st v0.8.0 -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes"
+```
+
+Don't forget to commit!
+
+5. Create a tag with the new version number, starting with a 'v', eg:
+
+```
+git tag v0.22.45
+```
+
+See [semver.org](http://semver.org/) on how to write a version number.
+
+
+
+6. push changes to github `git push --follow-tags`
+7. Verify travis tests passed and deployed sdist and wheel to PyPI
=====================================
doc/source/conf.py
=====================================
@@ -17,10 +17,10 @@ import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+
sys.path.insert(0, os.path.abspath('../../'))
sys.path.insert(0, os.path.abspath('../../pyorbital'))
-from pyorbital.version import __version__
+from pyorbital import __version__ # noqa
# -- General configuration -----------------------------------------------------
@@ -45,16 +45,16 @@ master_doc = 'index'
# General information about the project.
project = u'pyorbital'
-copyright = u'2012-2015, The Pytroll crew'
+copyright = u'2012-2015, 2018, The Pytroll crew'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
+# The short X.Y version.
+version = __version__.split('+')[0]
# The full version, including alpha/beta/rc tags.
release = __version__
-# The short X.Y version.
-version = ".".join(release.split(".")[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
=====================================
doc/source/index.rst
=====================================
@@ -28,8 +28,8 @@ Pyorbital has a module for parsing NORAD TLE-files
If no path is given pyorbital tries to read the earth observation TLE-files from celestrak.com
-Computing satellite postion
----------------------------
+Computing satellite position
+----------------------------
The orbital module enables computation of satellite position and velocity at a specific time:
>>> from pyorbital.orbital import Orbital
@@ -56,7 +56,7 @@ Use actual TLEs to increase accuracy
>>> orb.get_lonlatalt(dtobj)
(152.11564698762811, 20.475251739329622, 829.37355785502211)
-But since we are interesting knowing the position of the Suomi-NPP more than
+But since we are interested in knowing the position of the Suomi-NPP more than
two and half years from now (September 26, 2017) we can not rely on the current
TLEs, but rather need a TLE closer to the time of interest:
=====================================
pyorbital/__init__.py
=====================================
@@ -20,6 +20,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
+from .version import get_versions
+__version__ = get_versions()['version']
+del get_versions
def dt2np(utc_time):
=====================================
pyorbital/geoloc_instrument_definitions.py
=====================================
@@ -1,12 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2013, 2014, 2015, 2017 Martin Raspaud
+# Copyright (c) 2013 - 2018 PyTroll Community
# Author(s):
# Martin Raspaud <martin.raspaud at smhi.se>
# Mikhail Itkin <itkin.m at gmail.com>
+# Adam Dybbroe <adam.dybbroe at smhi.se>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -141,15 +142,21 @@ def avhrr_40_geom(scans_nb):
def viirs(scans_nb, scan_indices=slice(0, None),
- chn_pixels=6400, scan_lines=32):
+ chn_pixels=6400, scan_lines=32, scan_step=1):
"""Describe VIIRS instrument geometry, I-band by default.
VIIRS scans several lines simultaneously (there are 16 detectors for each
M-band, 32 detectors for each I-band) so the scan angles (and times) are
two-dimensional arrays, contrary to AVHRR for example.
+
+ scan_step: The increment in number of scans. E.g. if scan_step is 100 and
+ the number of scans (scans_nb) is 10 then these 10 scans are
+ distributed over the swath so that between each scan there are
+ 99 emtpy (excluded) scans
+
"""
entire_width = np.arange(chn_pixels)
- scan_points = entire_width[scan_indices]
+ scan_points = entire_width[scan_indices.astype('int')]
scan_pixels = len(scan_points)
''' initial angle 55.84 deg replaced with 56.28 deg found in
@@ -167,9 +174,10 @@ def viirs(scans_nb, scan_indices=slice(0, None),
npp = np.tile(scan, [scans_nb, 1]).T
# from the timestamp in the filenames, a granule takes 1:25.400 to record
- # (85.4 seconds) so 1.779166667 would be the duration of 1 scanline
- # dividing the duration of a single scan by a width of 6400 pixels results
- # in 0.0002779947917 seconds for each column of 32 pixels in the scanline
+ # (85.4 seconds) so 1.779166667 would be the duration of 1 scanline (48
+ # scans per granule) dividing the duration of a single scan by a width of
+ # 6400 pixels results in 0.0002779947917 seconds for each column of 32
+ # pixels in the scanline
# the individual times per pixel are probably wrong, unless the scanning
# behaves the same as for AVHRR, The VIIRS sensor rotates to allow internal
@@ -177,10 +185,13 @@ def viirs(scans_nb, scan_indices=slice(0, None),
# always moves in the same direction. more info @
# http://www.eoportal.org/directory/pres_NPOESSNationalPolarorbitingOperationalEnvironmentalSatelliteSystem.html
- offset = np.arange(scans_nb) * 1.779166667
- times = (np.tile(scan_points * 0.0002779947917,
- [np.int(scan_lines), np.int(scans_nb)])
- + np.expand_dims(offset, 1))
+ SEC_EACH_SCANCOLUMN = 0.0002779947917
+ sec_scan_duration = 1.779166667
+ times = np.tile(scan_points * SEC_EACH_SCANCOLUMN,
+ [np.int(scans_nb*scan_lines), 1])
+ offset = np.repeat(np.arange(scans_nb) *
+ sec_scan_duration*scan_step, scan_lines)
+ times += np.expand_dims(offset, 1)
# build the scan geometry object
return ScanGeometry(npp, times)
@@ -269,9 +280,9 @@ def mhs(scans_nb, edges_only=False):
"""
scan_len = 90 # 90 samples per scan
- scan_rate = 8/3. # single scan, seconds
+ scan_rate = 8 / 3. # single scan, seconds
scan_angle = -49.444 # swath, degrees
- sampling_interval = (8/3.-1)/90. # single view, seconds
+ sampling_interval = (8 / 3. - 1) / 90. # single view, seconds
if edges_only:
scan_points = np.array([0, scan_len - 1])
@@ -325,7 +336,7 @@ def hirs4(scans_nb, edges_only=False):
scan_len = 56 # 56 samples per scan
scan_rate = 6.4 # single scan, seconds
scan_angle = -49.5 # swath, degrees
- sampling_interval = abs(scan_rate)/scan_len # single view, seconds
+ sampling_interval = abs(scan_rate) / scan_len # single view, seconds
if edges_only:
scan_points = np.array([0, scan_len - 1])
@@ -376,7 +387,7 @@ def atms(scans_nb, edges_only=False):
"""
scan_len = 96 # 96 samples per scan
- scan_rate = 8/3. # single scan, seconds
+ scan_rate = 8 / 3. # single scan, seconds
scan_angle = -52.7 # swath, degrees
sampling_interval = 18e-3 # single view, seconds
@@ -403,3 +414,85 @@ def atms(scans_nb, edges_only=False):
def atms_edge_geom(scans_nb):
# we take only edge pixels
return atms(scans_nb, edges_only=True)
+
+################################################################
+#
+# OLCI
+#
+################################################################
+
+
+def olci(scans_nb, scan_points=None):
+ """Definition of the OLCI instrument.
+
+ Source: Sentinel-3 OLCI Coverage
+ https://sentinel.esa.int/web/sentinel/user-guides/sentinel-3-olci/coverage
+ """
+
+ if scan_points is None:
+ scan_len = 4000 # samples per scan
+ scan_points = np.arange(4000)
+ else:
+ scan_len = len(scan_points)
+ # scan_rate = 0.044 # single scan, seconds
+ scan_angle_west = 46.5 # swath, degrees
+ scan_angle_east = -22.1 # swath, degrees
+ # sampling_interval = 18e-3 # single view, seconds
+ # build the olci instrument scan line angles
+ scanline_angles = np.linspace(np.deg2rad(scan_angle_west),
+ np.deg2rad(scan_angle_east), scan_len)
+ inst = np.vstack((scanline_angles, np.zeros(scan_len,)))
+
+ inst = np.tile(inst[:, np.newaxis, :], [1, np.int(scans_nb), 1])
+
+ # building the corresponding times array
+ # times = (np.tile(scan_points * 0.000025 + 0.0025415, [scans_nb, 1])
+ # + np.expand_dims(offset, 1))
+
+ times = np.tile(np.zeros_like(scanline_angles), [np.int(scans_nb), 1])
+ # if apply_offset:
+ # offset = np.arange(np.int(scans_nb)) * frequency
+ # times += np.expand_dims(offset, 1)
+
+ return ScanGeometry(inst, times)
+
+
+def ascat(scan_nb, scan_points=None):
+ """ASCAT make two scans one to the left and one to the right of the
+ sub-satellite track.
+
+ """
+
+ if scan_points is None:
+ scan_len = 42 # samples per scan
+ scan_points = np.arange(42)
+ else:
+ scan_len = len(scan_points)
+
+ scan_angle_inner = -25.0 # swath, degrees
+ scan_angle_outer = -53.0 # swath, degrees
+ scan_rate = 3.74747474747 # single scan, seconds
+ if scan_len < 2:
+ raise ValueError("Need at least two scan points!")
+
+ sampling_interval = scan_rate / float(np.max(scan_points) + 1)
+
+ # build the Metop/ascat instrument scan line angles
+ scanline_angles_one = np.linspace(-np.deg2rad(scan_angle_outer),
+ -np.deg2rad(scan_angle_inner), 21)
+ scanline_angles_two = np.linspace(np.deg2rad(scan_angle_inner),
+ np.deg2rad(scan_angle_outer), 21)
+
+ scan_angles = np.concatenate(
+ [scanline_angles_one, scanline_angles_two])[scan_points]
+
+ inst = np.vstack((scan_angles, np.zeros(scan_len * 1,)))
+ inst = np.tile(inst[:, np.newaxis, :], [1, np.int(scan_nb), 1])
+
+ # building the corresponding times array
+ offset = np.arange(scan_nb) * scan_rate
+
+ times = (np.tile(scan_points * sampling_interval,
+ [np.int(scan_nb), 1]) + np.expand_dims(offset, 1))
+
+ return ScanGeometry(inst, times)
=====================================
pyorbital/orbital.py
=====================================
@@ -28,10 +28,25 @@
import warnings
from datetime import datetime, timedelta
-import numpy as np
+from scipy import optimize
+import numpy as np
from pyorbital import astronomy, dt2np, tlefile
+try:
+ import dask.array as da
+ has_dask = True
+except ImportError:
+ da = None
+ has_dask = False
+
+try:
+ import xarray as xr
+ has_xarray = True
+except ImportError:
+ xr = None
+ has_xarray = False
+
ECC_EPS = 1.0e-6 # Too low for computing further drops.
ECC_LIMIT_LOW = -1.0e-3
ECC_LIMIT_HIGH = 1.0 - ECC_EPS # Too close to 1
@@ -111,14 +126,22 @@ def get_observer_look(sat_lon, sat_lat, sat_alt, utc_time, lon, lat, alt):
az_ = np.arctan(-top_e / top_s)
- if hasattr(az_, 'chunks'):
- # dask array
- import dask.array as da
- az_ = da.where(top_s > 0, az_ + np.pi, az_)
- az_ = da.where(az_ < 0, az_ + 2 * np.pi, az_)
+ if has_xarray and isinstance(az_, xr.DataArray):
+ az_data = az_.data
else:
- az_[top_s > 0] += np.pi
- az_[az_ < 0] += 2 * np.pi
+ az_data = az_
+
+ if has_dask and isinstance(az_data, da.Array):
+ az_data = da.where(top_s > 0, az_data + np.pi, az_data)
+ az_data = da.where(az_data < 0, az_data + 2 * np.pi, az_data)
+ else:
+ az_data[np.where(top_s > 0)] += np.pi
+ az_data[np.where(az_data < 0)] += 2 * np.pi
+
+ if has_xarray and isinstance(az_, xr.DataArray):
+ az_.data = az_data
+ else:
+ az_ = az_data
rg_ = np.sqrt(rx * rx + ry * ry + rz * rz)
el_ = np.arcsin(top_z / rg_)
@@ -131,7 +154,7 @@ class Orbital(object):
"""Class for orbital computations.
The *satellite* parameter is the name of the satellite to work on and is
- used to retreive the right TLE data for internet or from *tle_file* in case
+ used to retrieve the right TLE data for internet or from *tle_file* in case
it is provided.
"""
@@ -337,8 +360,8 @@ class Orbital(object):
"""Compute the inverse of elevation."""
return -elevation(minutes)
- def get_root_secant(fun, start, end, tol=0.01):
- """Secant method."""
+ def get_root(fun, start, end, tol=0.01):
+ """Root finding scheme"""
x_0 = end
x_1 = start
fx_0 = fun(end)
@@ -346,11 +369,9 @@ class Orbital(object):
if abs(fx_0) < abs(fx_1):
fx_0, fx_1 = fx_1, fx_0
x_0, x_1 = x_1, x_0
- while abs(x_0 - x_1) > tol:
- x_n = x_1 - fx_1 * ((x_1 - x_0) / (fx_1 - fx_0))
- x_0, x_1 = x_1, x_n
- fx_0, fx_1 = fx_1, fun(x_n)
- return x_1
+
+ x_n = optimize.brentq(fun, x_0, x_1)
+ return x_n
def get_max_parab(fun, start, end, tol=0.01):
"""Successive parabolic interpolation."""
@@ -385,7 +406,7 @@ class Orbital(object):
risetime = None
falltime = None
for guess in zcs:
- horizon_mins = get_root_secant(
+ horizon_mins = get_root(
elevation, guess, guess + 1.0, tol=tol / 60.0)
horizon_time = utc_time + timedelta(minutes=horizon_mins)
if elev[guess] < 0:
=====================================
pyorbital/tests/test_aiaa.py
=====================================
@@ -130,7 +130,7 @@ class AIAAIntegrationTest(unittest.TestCase):
delta_pos = 5e-6 # km = 5 mm
delta_vel = 5e-9 # km/s = 5 um/s
- delta_time = 1e-3 # 1 milisecond
+ delta_time = 1e-3 # 1 millisecond
self.assertTrue(abs(res[0] - pos[0]) < delta_pos)
self.assertTrue(abs(res[1] - pos[1]) < delta_pos)
self.assertTrue(abs(res[2] - pos[2]) < delta_pos)
=====================================
pyorbital/tests/test_geoloc.py
=====================================
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, 2017 Martin Raspaud
+# Copyright (c) 2014, 2017, 2018 Martin Raspaud
# Author(s):
@@ -29,7 +29,13 @@ from datetime import datetime, timedelta
import numpy as np
from pyorbital.geoloc import ScanGeometry, geodetic_lat, qrotate, subpoint
-from pyorbital.geoloc_instrument_definitions import avhrr, viirs, amsua, mhs, hirs4, atms
+from pyorbital.geoloc_instrument_definitions import (avhrr,
+ viirs,
+ amsua,
+ mhs,
+ hirs4,
+ atms,
+ ascat)
class TestQuaternion(unittest.TestCase):
@@ -189,8 +195,17 @@ class TestGeolocDefs(unittest.TestCase):
"""
geom = viirs(1, np.array([0, 3200, 6399]))
expected_fovs = np.array([
- np.tile(np.array([[ 0.98, -0. , -0.98]]), [32,1]),
- np.tile(np.array([[ 0. , -0. , 0 ]]), [32,1])], dtype=np.float)
+ np.tile(np.array([[0.98, -0., -0.98]]), [32, 1]),
+ np.tile(np.array([[0., -0., 0]]), [32, 1])], dtype=np.float)
+
+ self.assertTrue(np.allclose(geom.fovs,
+ expected_fovs, rtol=1e-2, atol=1e-2))
+
+ geom = viirs(2, np.array([0, 3200, 6399]))
+ expected_fovs = np.array([
+ np.tile(np.array([[0.98, -0., -0.98]]), [32*2, 1]),
+ np.tile(np.array([[0., -0., 0]]), [32*2, 1])], dtype=np.float)
+
self.assertTrue(np.allclose(geom.fovs,
expected_fovs, rtol=1e-2, atol=1e-2))
@@ -199,9 +214,9 @@ class TestGeolocDefs(unittest.TestCase):
"""
geom = amsua(1)
expected_fovs = np.array([
- [[ 0.84, 0.78, 0.73, 0.67, 0.61, 0.55, 0.49, 0.44, 0.38,
- 0.32, 0.26, 0.2 , 0.15, 0.09, 0.03, -0.03, -0.09, -0.15,
- -0.2 , -0.26, -0.32, -0.38, -0.44, -0.49, -0.55, -0.61, -0.67,
+ [[0.84, 0.78, 0.73, 0.67, 0.61, 0.55, 0.49, 0.44, 0.38,
+ 0.32, 0.26, 0.2, 0.15, 0.09, 0.03, -0.03, -0.09, -0.15,
+ -0.2, -0.26, -0.32, -0.38, -0.44, -0.49, -0.55, -0.61, -0.67,
-0.73, -0.78, -0.84]],
np.zeros((1, 30))], dtype=np.float)
self.assertTrue(np.allclose(geom.fovs, expected_fovs, rtol=1e-2, atol=1e-2))
@@ -211,16 +226,16 @@ class TestGeolocDefs(unittest.TestCase):
"""
geom = mhs(1)
expected_fovs = np.array([
- [[ 0.86, 0.84, 0.82, 0.8 , 0.79, 0.77, 0.75, 0.73, 0.71,
- 0.69, 0.67, 0.65, 0.63, 0.61, 0.59, 0.57, 0.55, 0.53,
- 0.51, 0.49, 0.48, 0.46, 0.44, 0.42, 0.4 , 0.38, 0.36,
- 0.34, 0.32, 0.3 , 0.28, 0.26, 0.24, 0.22, 0.2 , 0.18,
- 0.16, 0.15, 0.13, 0.11, 0.09, 0.07, 0.05, 0.03, 0.01,
+ [[0.86, 0.84, 0.82, 0.8, 0.79, 0.77, 0.75, 0.73, 0.71,
+ 0.69, 0.67, 0.65, 0.63, 0.61, 0.59, 0.57, 0.55, 0.53,
+ 0.51, 0.49, 0.48, 0.46, 0.44, 0.42, 0.4, 0.38, 0.36,
+ 0.34, 0.32, 0.3, 0.28, 0.26, 0.24, 0.22, 0.2, 0.18,
+ 0.16, 0.15, 0.13, 0.11, 0.09, 0.07, 0.05, 0.03, 0.01,
-0.01, -0.03, -0.05, -0.07, -0.09, -0.11, -0.13, -0.15, -0.16,
- -0.18, -0.2 , -0.22, -0.24, -0.26, -0.28, -0.3 , -0.32, -0.34,
- -0.36, -0.38, -0.4 , -0.42, -0.44, -0.46, -0.48, -0.49, -0.51,
+ -0.18, -0.2, -0.22, -0.24, -0.26, -0.28, -0.3, -0.32, -0.34,
+ -0.36, -0.38, -0.4, -0.42, -0.44, -0.46, -0.48, -0.49, -0.51,
-0.53, -0.55, -0.57, -0.59, -0.61, -0.63, -0.65, -0.67, -0.69,
- -0.71, -0.73, -0.75, -0.77, -0.79, -0.8 , -0.82, -0.84, -0.86]],
+ -0.71, -0.73, -0.75, -0.77, -0.79, -0.8, -0.82, -0.84, -0.86]],
np.zeros((1, 90))], dtype=np.float)
self.assertTrue(np.allclose(geom.fovs,
expected_fovs, rtol=1e-2, atol=1e-2))
@@ -230,12 +245,12 @@ class TestGeolocDefs(unittest.TestCase):
"""
geom = hirs4(1)
expected_fovs = np.array([
- [[ 0.86, 0.83, 0.8 , 0.77, 0.74, 0.71, 0.68, 0.64, 0.61,
- 0.58, 0.55, 0.52, 0.49, 0.46, 0.42, 0.39, 0.36, 0.33,
- 0.3 , 0.27, 0.24, 0.2 , 0.17, 0.14, 0.11, 0.08, 0.05,
- 0.02, -0.02, -0.05, -0.08, -0.11, -0.14, -0.17, -0.2 , -0.24,
- -0.27, -0.3 , -0.33, -0.36, -0.39, -0.42, -0.46, -0.49, -0.52,
- -0.55, -0.58, -0.61, -0.64, -0.68, -0.71, -0.74, -0.77, -0.8 ,
+ [[0.86, 0.83, 0.8, 0.77, 0.74, 0.71, 0.68, 0.64, 0.61,
+ 0.58, 0.55, 0.52, 0.49, 0.46, 0.42, 0.39, 0.36, 0.33,
+ 0.3, 0.27, 0.24, 0.2, 0.17, 0.14, 0.11, 0.08, 0.05,
+ 0.02, -0.02, -0.05, -0.08, -0.11, -0.14, -0.17, -0.2, -0.24,
+ -0.27, -0.3, -0.33, -0.36, -0.39, -0.42, -0.46, -0.49, -0.52,
+ -0.55, -0.58, -0.61, -0.64, -0.68, -0.71, -0.74, -0.77, -0.8,
-0.83, -0.86]],
np.zeros((1, 56))], dtype=np.float)
self.assertTrue(np.allclose(geom.fovs,
@@ -246,21 +261,50 @@ class TestGeolocDefs(unittest.TestCase):
"""
geom = atms(1)
expected_fovs = np.array([
- [[ 0.92, 0.9 , 0.88, 0.86, 0.84, 0.82, 0.8 , 0.78, 0.76,
- 0.75, 0.73, 0.71, 0.69, 0.67, 0.65, 0.63, 0.61, 0.59,
- 0.57, 0.55, 0.53, 0.51, 0.49, 0.47, 0.46, 0.44, 0.42,
- 0.4 , 0.38, 0.36, 0.34, 0.32, 0.3 , 0.28, 0.26, 0.24,
- 0.22, 0.2 , 0.18, 0.16, 0.15, 0.13, 0.11, 0.09, 0.07,
- 0.05, 0.03, 0.01, -0.01, -0.03, -0.05, -0.07, -0.09, -0.11,
- -0.13, -0.15, -0.16, -0.18, -0.2 , -0.22, -0.24, -0.26, -0.28,
- -0.3 , -0.32, -0.34, -0.36, -0.38, -0.4 , -0.42, -0.44, -0.46,
+ [[0.92, 0.9, 0.88, 0.86, 0.84, 0.82, 0.8, 0.78, 0.76,
+ 0.75, 0.73, 0.71, 0.69, 0.67, 0.65, 0.63, 0.61, 0.59,
+ 0.57, 0.55, 0.53, 0.51, 0.49, 0.47, 0.46, 0.44, 0.42,
+ 0.4, 0.38, 0.36, 0.34, 0.32, 0.3, 0.28, 0.26, 0.24,
+ 0.22, 0.2, 0.18, 0.16, 0.15, 0.13, 0.11, 0.09, 0.07,
+ 0.05, 0.03, 0.01, -0.01, -0.03, -0.05, -0.07, -0.09, -0.11,
+ -0.13, -0.15, -0.16, -0.18, -0.2, -0.22, -0.24, -0.26, -0.28,
+ -0.3, -0.32, -0.34, -0.36, -0.38, -0.4, -0.42, -0.44, -0.46,
-0.47, -0.49, -0.51, -0.53, -0.55, -0.57, -0.59, -0.61, -0.63,
- -0.65, -0.67, -0.69, -0.71, -0.73, -0.75, -0.76, -0.78, -0.8 ,
- -0.82, -0.84, -0.86, -0.88, -0.9 , -0.92]],
+ -0.65, -0.67, -0.69, -0.71, -0.73, -0.75, -0.76, -0.78, -0.8,
+ -0.82, -0.84, -0.86, -0.88, -0.9, -0.92]],
np.zeros((1, 96))], dtype=np.float)
self.assertTrue(np.allclose(geom.fovs,
expected_fovs, rtol=1e-2, atol=1e-2))
+ def test_ascat(self):
+ """Test the definition of the ASCAT instrument onboard Metop"""
+
+ geom = ascat(1)
+ expected_fovs = np.array([
+ [[0.9250245, 0.90058989, 0.87615528, 0.85172067,
+ 0.82728607, 0.80285146, 0.77841685, 0.75398224,
+ 0.72954763, 0.70511302, 0.68067841, 0.6562438,
+ 0.63180919, 0.60737458, 0.58293997, 0.55850536,
+ 0.53407075, 0.50963614, 0.48520153, 0.46076692,
+ 0.43633231, -0.43633231, -0.46076692, -0.48520153,
+ -0.50963614, -0.53407075, -0.55850536, -0.58293997,
+ -0.60737458, -0.63180919, -0.6562438, -0.68067841,
+ -0.70511302, -0.72954763, -0.75398224, -0.77841685,
+ -0.80285146, -0.82728607, -0.85172067, -0.87615528,
+ -0.90058989, -0.9250245]], np.zeros((1, 42))], dtype=np.float)
+
+ self.assertTrue(np.allclose(
+ geom.fovs, expected_fovs, rtol=1e-2, atol=1e-2))
+ geom = ascat(1, np.array([0, 41]))
+ expected_fovs = np.array([[[0.9250245, -0.9250245]],
+ [[0., 0.]]], dtype=np.float)
+ self.assertTrue(np.allclose(
+ geom.fovs, expected_fovs, rtol=1e-2, atol=1e-2))
+
+ geom = ascat(1, np.array([0, -1]))
+ self.assertTrue(np.allclose(
+ geom.fovs, expected_fovs, rtol=1e-2, atol=1e-2))
+
def suite():
"""The suite for test_geoloc
=====================================
pyorbital/tests/test_orbital.py
=====================================
@@ -38,54 +38,71 @@ class Test(unittest.TestCase):
def test_get_orbit_number(self):
"""Testing getting the orbitnumber from the tle"""
sat = orbital.Orbital("NPP",
- line1="1 37849U 11061A 12017.90990040 -.00000112 00000-0 -32693-4 0 772",
- line2="2 37849 98.7026 317.8811 0001845 92.4533 267.6830 14.19582686 11574")
+ line1="1 37849U 11061A 12017.90990040 "
+ "-.00000112 00000-0 -32693-4 0 772",
+ line2="2 37849 98.7026 317.8811 0001845 "
+ "92.4533 267.6830 14.19582686 11574")
dobj = datetime(2012, 1, 18, 8, 4, 19)
orbnum = sat.get_orbit_number(dobj)
self.assertEqual(orbnum, 1163)
def test_sublonlat(self):
sat = orbital.Orbital("ISS (ZARYA)",
- line1="1 25544U 98067A 03097.78853147 .00021906 00000-0 28403-3 0 8652",
- line2="2 25544 51.6361 13.7980 0004256 35.6671 59.2566 15.58778559250029")
+ line1="1 25544U 98067A 03097.78853147 "
+ ".00021906 00000-0 28403-3 0 8652",
+ line2="2 25544 51.6361 13.7980 0004256 "
+ "35.6671 59.2566 15.58778559250029")
d = datetime(2003, 3, 23, 0, 3, 22)
lon, lat, alt = sat.get_lonlatalt(d)
expected_lon = -68.199894472013213
expected_lat = 23.159747677881075
expected_alt = 392.01953430856935
- self.assertTrue(np.abs(lon - expected_lon) < eps_deg, 'Calculation of sublon failed')
- self.assertTrue(np.abs(lat - expected_lat) < eps_deg, 'Calculation of sublat failed')
- self.assertTrue(np.abs(alt - expected_alt) < eps_deg, 'Calculation of altitude failed')
+ self.assertTrue(np.abs(lon - expected_lon) < eps_deg,
+ 'Calculation of sublon failed')
+ self.assertTrue(np.abs(lat - expected_lat) < eps_deg,
+ 'Calculation of sublat failed')
+ self.assertTrue(np.abs(alt - expected_alt) < eps_deg,
+ 'Calculation of altitude failed')
def test_observer_look(self):
sat = orbital.Orbital("ISS (ZARYA)",
- line1="1 25544U 98067A 03097.78853147 .00021906 00000-0 28403-3 0 8652",
- line2="2 25544 51.6361 13.7980 0004256 35.6671 59.2566 15.58778559250029")
+ line1="1 25544U 98067A 03097.78853147 "
+ ".00021906 00000-0 28403-3 0 8652",
+ line2="2 25544 51.6361 13.7980 0004256 "
+ "35.6671 59.2566 15.58778559250029")
d = datetime(2003, 3, 23, 0, 3, 22)
az, el = sat.get_observer_look(d, -84.39733, 33.775867, 0)
expected_az = 122.45169655331965
expected_el = 1.9800219611255456
- self.assertTrue(np.abs(az - expected_az) < eps_deg, 'Calculation of azimut failed')
- self.assertTrue(np.abs(el - expected_el) < eps_deg, 'Calculation of elevation failed')
+ self.assertTrue(np.abs(az - expected_az) < eps_deg,
+ 'Calculation of azimut failed')
+ self.assertTrue(np.abs(el - expected_el) < eps_deg,
+ 'Calculation of elevation failed')
def test_orbit_num_an(self):
sat = orbital.Orbital("METOP-A",
- line1="1 29499U 06044A 11254.96536486 .00000092 00000-0 62081-4 0 5221",
- line2="2 29499 98.6804 312.6735 0001758 111.9178 248.2152 14.21501774254058")
+ line1="1 29499U 06044A 11254.96536486 "
+ ".00000092 00000-0 62081-4 0 5221",
+ line2="2 29499 98.6804 312.6735 0001758 "
+ "111.9178 248.2152 14.21501774254058")
d = datetime(2011, 9, 14, 5, 30)
self.assertEqual(sat.get_orbit_number(d), 25437)
def test_orbit_num_non_an(self):
sat = orbital.Orbital("METOP-A",
- line1="1 29499U 06044A 13060.48822809 .00000017 00000-0 27793-4 0 9819",
- line2="2 29499 98.6639 121.6164 0001449 71.9056 43.3132 14.21510544330271")
+ line1="1 29499U 06044A 13060.48822809 "
+ ".00000017 00000-0 27793-4 0 9819",
+ line2="2 29499 98.6639 121.6164 0001449 "
+ "71.9056 43.3132 14.21510544330271")
dt = np.timedelta64(98, 'm')
self.assertEqual(sat.get_orbit_number(sat.tle.epoch + dt), 33028)
def test_orbit_num_equator(self):
sat = orbital.Orbital("SUOMI NPP",
- line1="1 37849U 11061A 13061.24611272 .00000048 00000-0 43679-4 0 4334",
- line2="2 37849 98.7444 1.0588 0001264 63.8791 102.8546 14.19528338 69643")
+ line1="1 37849U 11061A 13061.24611272 "
+ ".00000048 00000-0 43679-4 0 4334",
+ line2="2 37849 98.7444 1.0588 0001264 "
+ "63.8791 102.8546 14.19528338 69643")
t1 = datetime(2013, 3, 2, 22, 2, 25)
t2 = datetime(2013, 3, 2, 22, 2, 26)
on1 = sat.get_orbit_number(t1)
@@ -100,14 +117,120 @@ class Test(unittest.TestCase):
def test_get_next_passes_apogee(self):
"""Regression test #22."""
- line1 = "1 24793U 97020B 18065.48735489 .00000075 00000-0 19863-4 0 9994"
- line2 = "2 24793 86.3994 209.3241 0002020 89.8714 270.2713 14.34246429 90794"
+ line1 = "1 24793U 97020B 18065.48735489 " \
+ ".00000075 00000-0 19863-4 0 9994"
+ line2 = "2 24793 86.3994 209.3241 0002020 " \
+ "89.8714 270.2713 14.34246429 90794"
orb = orbital.Orbital('IRIDIUM 7 [+]', line1=line1, line2=line2)
d = datetime(2018, 3, 7, 3, 30, 15)
res = orb.get_next_passes(d, 1, 170.556, -43.368, 0.5, horizon=40)
- self.assertTrue(abs(res[0][2] - datetime(2018, 3, 7, 3, 48, 13, 178439)) < timedelta(seconds=0.01))
-
+ self.assertTrue(abs(
+ res[0][2] - datetime(2018, 3, 7, 3, 48, 13, 178439)) <
+ timedelta(seconds=0.01))
+
+ def test_get_next_passes_tricky(self):
+ """ Check issue #34 for reference """
+ line1 = "1 43125U 18004Q 18251.42128650 " \
+ "+.00001666 +00000-0 +73564-4 0 9991"
+
+ line2 = "2 43125 097.5269 314.3317 0010735 "\
+ "157.6344 202.5362 15.23132245036381"
+
+ orb = orbital.Orbital('LEMUR-2-BROWNCOW', line1=line1, line2=line2)
+ d = datetime(2018, 9, 8)
+
+ res = orb.get_next_passes(d, 72, -8.174163, 51.953319, 0.05, horizon=5)
+
+ self.assertTrue(abs(
+ res[0][2] - datetime(2018, 9, 8, 9, 5, 46, 375248)) <
+ timedelta(seconds=0.01))
+ self.assertTrue(abs(
+ res[-1][2] - datetime(2018, 9, 10, 22, 15, 3, 143469)) <
+ timedelta(seconds=0.01))
+
+ self.assertTrue(len(res) == 15)
+
+
+class TestGetObserverLook(unittest.TestCase):
+ """Test the get_observer_look function"""
+
+ def setUp(self):
+ self.t = datetime(2018, 1, 1, 0, 0, 0)
+ self.sat_lon = np.array([[-89.5, -89.4], [-89.3, -89.2]])
+ self.sat_lat = np.array([[45.5, 45.4], [45.3, 45.2]])
+ self.sat_alt = np.array([[35786, 35786], [35786, 35786]])
+ self.lon = np.array([[-85.5, -85.4], [-85.3, -85.2]])
+ self.lat = np.array([[40.5, 40.4], [40.3, 40.2]])
+ self.alt = np.zeros((2, 2))
+ self.exp_azi = np.array([[331.00275902, 330.95954165],
+ [330.91642994, 330.87342384]])
+ self.exp_elev = np.array([[83.18070976, 83.17788976],
+ [83.17507167, 83.1722555]])
+
+ def test_basic_numpy(self):
+ """Test with numpy array inputs"""
+ from pyorbital import orbital
+ azi, elev = orbital.get_observer_look(self.sat_lon, self.sat_lat,
+ self.sat_alt, self.t,
+ self.lon, self.lat, self.alt)
+ np.testing.assert_allclose(azi, self.exp_azi)
+ np.testing.assert_allclose(elev, self.exp_elev)
+
+ def test_basic_dask(self):
+ """Test with dask array inputs"""
+ from pyorbital import orbital
+ import dask.array as da
+ sat_lon = da.from_array(self.sat_lon, chunks=2)
+ sat_lat = da.from_array(self.sat_lat, chunks=2)
+ sat_alt = da.from_array(self.sat_alt, chunks=2)
+ lon = da.from_array(self.lon, chunks=2)
+ lat = da.from_array(self.lat, chunks=2)
+ alt = da.from_array(self.alt, chunks=2)
+ azi, elev = orbital.get_observer_look(sat_lon, sat_lat,
+ sat_alt, self.t,
+ lon, lat, alt)
+ np.testing.assert_allclose(azi.compute(), self.exp_azi)
+ np.testing.assert_allclose(elev.compute(), self.exp_elev)
+
+ def test_xarray_with_numpy(self):
+ """Test with xarray DataArray with numpy array as inputs"""
+ from pyorbital import orbital
+ import xarray as xr
+
+ def _xarr_conv(input):
+ return xr.DataArray(input)
+ sat_lon = _xarr_conv(self.sat_lon)
+ sat_lat = _xarr_conv(self.sat_lat)
+ sat_alt = _xarr_conv(self.sat_alt)
+ lon = _xarr_conv(self.lon)
+ lat = _xarr_conv(self.lat)
+ alt = _xarr_conv(self.alt)
+ azi, elev = orbital.get_observer_look(sat_lon, sat_lat,
+ sat_alt, self.t,
+ lon, lat, alt)
+ np.testing.assert_allclose(azi.data, self.exp_azi)
+ np.testing.assert_allclose(elev.data, self.exp_elev)
+
+ def test_xarray_with_dask(self):
+ """Test with xarray DataArray with dask array as inputs"""
+ from pyorbital import orbital
+ import dask.array as da
+ import xarray as xr
+
+ def _xarr_conv(input):
+ return xr.DataArray(da.from_array(input, chunks=2))
+ sat_lon = _xarr_conv(self.sat_lon)
+ sat_lat = _xarr_conv(self.sat_lat)
+ sat_alt = _xarr_conv(self.sat_alt)
+ lon = _xarr_conv(self.lon)
+ lat = _xarr_conv(self.lat)
+ alt = _xarr_conv(self.alt)
+ azi, elev = orbital.get_observer_look(sat_lon, sat_lat,
+ sat_alt, self.t,
+ lon, lat, alt)
+ np.testing.assert_allclose(azi.data.compute(), self.exp_azi)
+ np.testing.assert_allclose(elev.data.compute(), self.exp_elev)
def suite():
@@ -116,5 +239,6 @@ def suite():
loader = unittest.TestLoader()
mysuite = unittest.TestSuite()
mysuite.addTest(loader.loadTestsFromTestCase(Test))
+ mysuite.addTest(loader.loadTestsFromTestCase(TestGetObserverLook))
return mysuite
=====================================
pyorbital/tlefile.py
=====================================
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2011, 2012, 2013, 2014, 2015.
+# Copyright (c) 2011 - 2018
# Author(s):
@@ -78,63 +78,9 @@ SATELLITES = read_platform_numbers(in_upper=True, num_as_int=False)
The platform numbers are given in a file $PPP_CONFIG/platforms.txt
in the following format:
-# Mappings between satellite catalogue numbers and corresponding
-# platform names from OSCAR.
-ALOS-2 39766
-CloudSat 29107
-CryoSat-2 36508
-CSK-1 31598
-CSK-2 32376
-CSK-3 33412
-CSK-4 37216
-DMSP-F15 25991
-DMSP-F16 28054
-DMSP-F17 29522
-DMSP-F18 35951
-DMSP-F19 39630
-EOS-Aqua 27424
-EOS-Aura 28376
-EOS-Terra 25994
-FY-2D 29640
-FY-2E 33463
-FY-2F 38049
-FY-2G 40367
-FY-3A 32958
-FY-3B 37214
-FY-3C 39260
-GOES-13 29155
-GOES-14 35491
-GOES-15 36411
-Himawari-6 28622
-Himawari-7 28937
-Himawari-8 40267
-INSAT-3A 27714
-INSAT-3C 27298
-INSAT-3D 39216
-JASON-2 33105
-Kalpana-1 27525
-Landsat-7 25682
-Landsat-8 39084
-Meteosat-7 24932
-Meteosat-8 27509
-Meteosat-9 28912
-Meteosat-10 38552
-Metop-A 29499
-Metop-B 38771
-NOAA-15 25338
-NOAA-16 26536
-NOAA-17 27453
-NOAA-18 28654
-NOAA-19 33591
-RadarSat-2 32382
-Sentinel-1A 39634
-SMOS 36036
-SPOT-5 27421
-SPOT-6 38755
-SPOT-7 40053
-Suomi-NPP 37849
-TanDEM-X 36605
-TerraSAR-X 31698
+.. literalinclude:: ../../etc/platforms.txt
+ :language: text
+ :lines: 4-
'''
@@ -343,5 +289,6 @@ def main():
tle_data = read('Noaa-19')
print(tle_data)
+
if __name__ == '__main__':
main()
=====================================
pyorbital/version.py
=====================================
@@ -1,26 +1,520 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Copyright (c) 2014, 2015 Martin Raspaud
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by githubs download-from-tag
+# feature). Distribution tarballs (built by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
-# Author(s):
+# This file is released into the public domain. Generated by
+# versioneer-0.18 (https://github.com/warner/python-versioneer)
-# Martin Raspaud <martin.raspaud at smhi.se>
+"""Git implementation of _version.py."""
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+import errno
+import os
+import re
+import subprocess
+import sys
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+def get_keywords():
+ """Get the keywords needed to look up the version information."""
+ # these strings will be replaced by git during git-archive.
+ # setup.py/versioneer.py will grep for the variable names, so they must
+ # each be defined on a line of their own. _version.py will just call
+ # get_keywords().
+ git_refnames = " (HEAD -> master, tag: v1.4.0)"
+ git_full = "eb3dadc23abc7e7a192078cd0036622a120e4d49"
+ git_date = "2018-10-23 11:29:26 +0200"
+ keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
+ return keywords
-"""Version file.
-"""
-__version__ = "v1.3.1"
+class VersioneerConfig:
+ """Container for Versioneer configuration parameters."""
+
+
+def get_config():
+ """Create, populate and return the VersioneerConfig() object."""
+ # these strings are filled in when 'setup.py versioneer' creates
+ # _version.py
+ cfg = VersioneerConfig()
+ cfg.VCS = "git"
+ cfg.style = "pep440"
+ cfg.tag_prefix = "v"
+ cfg.parentdir_prefix = "None"
+ cfg.versionfile_source = "pyorbital/version.py"
+ cfg.verbose = False
+ return cfg
+
+
+class NotThisMethod(Exception):
+ """Exception raised if a method is not valid for the current scenario."""
+
+
+LONG_VERSION_PY = {}
+HANDLERS = {}
+
+
+def register_vcs_handler(vcs, method): # decorator
+ """Decorator to mark a method as the handler for a particular VCS."""
+ def decorate(f):
+ """Store f in HANDLERS[vcs][method]."""
+ if vcs not in HANDLERS:
+ HANDLERS[vcs] = {}
+ HANDLERS[vcs][method] = f
+ return f
+ return decorate
+
+
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+ env=None):
+ """Call the given command(s)."""
+ assert isinstance(commands, list)
+ p = None
+ for c in commands:
+ try:
+ dispcmd = str([c] + args)
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+ stdout=subprocess.PIPE,
+ stderr=(subprocess.PIPE if hide_stderr
+ else None))
+ break
+ except EnvironmentError:
+ e = sys.exc_info()[1]
+ if e.errno == errno.ENOENT:
+ continue
+ if verbose:
+ print("unable to run %s" % dispcmd)
+ print(e)
+ return None, None
+ else:
+ if verbose:
+ print("unable to find command, tried %s" % (commands,))
+ return None, None
+ stdout = p.communicate()[0].strip()
+ if sys.version_info[0] >= 3:
+ stdout = stdout.decode()
+ if p.returncode != 0:
+ if verbose:
+ print("unable to run %s (error)" % dispcmd)
+ print("stdout was %s" % stdout)
+ return None, p.returncode
+ return stdout, p.returncode
+
+
+def versions_from_parentdir(parentdir_prefix, root, verbose):
+ """Try to determine the version from the parent directory name.
+
+ Source tarballs conventionally unpack into a directory that includes both
+ the project name and a version string. We will also support searching up
+ two directory levels for an appropriately named parent directory
+ """
+ rootdirs = []
+
+ for i in range(3):
+ dirname = os.path.basename(root)
+ if dirname.startswith(parentdir_prefix):
+ return {"version": dirname[len(parentdir_prefix):],
+ "full-revisionid": None,
+ "dirty": False, "error": None, "date": None}
+ else:
+ rootdirs.append(root)
+ root = os.path.dirname(root) # up a level
+
+ if verbose:
+ print("Tried directories %s but none started with prefix %s" %
+ (str(rootdirs), parentdir_prefix))
+ raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+
+
+ at register_vcs_handler("git", "get_keywords")
+def git_get_keywords(versionfile_abs):
+ """Extract version information from the given file."""
+ # the code embedded in _version.py can just fetch the value of these
+ # keywords. When used from setup.py, we don't want to import _version.py,
+ # so we do it with a regexp instead. This function is not used from
+ # _version.py.
+ keywords = {}
+ try:
+ f = open(versionfile_abs, "r")
+ for line in f.readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["full"] = mo.group(1)
+ if line.strip().startswith("git_date ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["date"] = mo.group(1)
+ f.close()
+ except EnvironmentError:
+ pass
+ return keywords
+
+
+ at register_vcs_handler("git", "keywords")
+def git_versions_from_keywords(keywords, tag_prefix, verbose):
+ """Get version information from git keywords."""
+ if not keywords:
+ raise NotThisMethod("no keywords at all, weird")
+ date = keywords.get("date")
+ if date is not None:
+ # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
+ # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
+ # -like" string, which we must then edit to make compliant), because
+ # it's been around since git-1.5.3, and it's too difficult to
+ # discover which version we're using, or to work around using an
+ # older one.
+ date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ refnames = keywords["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print("keywords are unexpanded, not using")
+ raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+ # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+ TAG = "tag: "
+ tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
+ if not tags:
+ # Either we're using git < 1.8.3, or there really are no tags. We use
+ # a heuristic: assume all version tags have a digit. The old git %d
+ # expansion behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us distinguish
+ # between branches and tags. By ignoring refnames without digits, we
+ # filter out many common branch names like "release" and
+ # "stabilization", as well as "HEAD" and "master".
+ tags = set([r for r in refs if re.search(r'\d', r)])
+ if verbose:
+ print("discarding '%s', no digits" % ",".join(refs - tags))
+ if verbose:
+ print("likely tags: %s" % ",".join(sorted(tags)))
+ for ref in sorted(tags):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print("picking %s" % r)
+ return {"version": r,
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False, "error": None,
+ "date": date}
+ # no suitable tags, so version is "0+unknown", but full hex is still there
+ if verbose:
+ print("no suitable tags, using unknown + full revision id")
+ return {"version": "0+unknown",
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False, "error": "no suitable tags", "date": None}
+
+
+ at register_vcs_handler("git", "pieces_from_vcs")
+def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ """Get version from 'git describe' in the root of the source tree.
+
+ This only gets called if the git-archive 'subst' keywords were *not*
+ expanded, and _version.py hasn't already been rewritten with a short
+ version string, meaning we're inside a checked out source tree.
+ """
+ GITS = ["git"]
+ if sys.platform == "win32":
+ GITS = ["git.cmd", "git.exe"]
+
+ out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+ hide_stderr=True)
+ if rc != 0:
+ if verbose:
+ print("Directory %s not under git control" % root)
+ raise NotThisMethod("'git rev-parse --git-dir' returned error")
+
+ # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+ # if there isn't one, this yields HEX[-dirty] (no NUM)
+ describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+ "--always", "--long",
+ "--match", "%s*" % tag_prefix],
+ cwd=root)
+ # --long was added in git-1.5.5
+ if describe_out is None:
+ raise NotThisMethod("'git describe' failed")
+ describe_out = describe_out.strip()
+ full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
+ if full_out is None:
+ raise NotThisMethod("'git rev-parse' failed")
+ full_out = full_out.strip()
+
+ pieces = {}
+ pieces["long"] = full_out
+ pieces["short"] = full_out[:7] # maybe improved later
+ pieces["error"] = None
+
+ # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+ # TAG might have hyphens.
+ git_describe = describe_out
+
+ # look for -dirty suffix
+ dirty = git_describe.endswith("-dirty")
+ pieces["dirty"] = dirty
+ if dirty:
+ git_describe = git_describe[:git_describe.rindex("-dirty")]
+
+ # now we have TAG-NUM-gHEX or HEX
+
+ if "-" in git_describe:
+ # TAG-NUM-gHEX
+ mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+ if not mo:
+ # unparseable. Maybe git-describe is misbehaving?
+ pieces["error"] = ("unable to parse git-describe output: '%s'"
+ % describe_out)
+ return pieces
+
+ # tag
+ full_tag = mo.group(1)
+ if not full_tag.startswith(tag_prefix):
+ if verbose:
+ fmt = "tag '%s' doesn't start with prefix '%s'"
+ print(fmt % (full_tag, tag_prefix))
+ pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
+ % (full_tag, tag_prefix))
+ return pieces
+ pieces["closest-tag"] = full_tag[len(tag_prefix):]
+
+ # distance: number of commits since tag
+ pieces["distance"] = int(mo.group(2))
+
+ # commit: short hex revision ID
+ pieces["short"] = mo.group(3)
+
+ else:
+ # HEX: no tags
+ pieces["closest-tag"] = None
+ count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+ cwd=root)
+ pieces["distance"] = int(count_out) # total number of commits
+
+ # commit date: see ISO-8601 comment in git_versions_from_keywords()
+ date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
+ cwd=root)[0].strip()
+ pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+
+ return pieces
+
+
+def plus_or_dot(pieces):
+ """Return a + if we don't already have one, else return a ."""
+ if "+" in pieces.get("closest-tag", ""):
+ return "."
+ return "+"
+
+
+def render_pep440(pieces):
+ """Build up version string, with post-release "local version identifier".
+
+ Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+ get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
+
+ Exceptions:
+ 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += plus_or_dot(pieces)
+ rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ else:
+ # exception #1
+ rendered = "0+untagged.%d.g%s" % (pieces["distance"],
+ pieces["short"])
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ return rendered
+
+
+def render_pep440_pre(pieces):
+ """TAG[.post.devDISTANCE] -- No -dirty.
+
+ Exceptions:
+ 1: no tags. 0.post.devDISTANCE
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"]:
+ rendered += ".post.dev%d" % pieces["distance"]
+ else:
+ # exception #1
+ rendered = "0.post.dev%d" % pieces["distance"]
+ return rendered
+
+
+def render_pep440_post(pieces):
+ """TAG[.postDISTANCE[.dev0]+gHEX] .
+
+ The ".dev0" means dirty. Note that .dev0 sorts backwards
+ (a dirty tree will appear "older" than the corresponding clean one),
+ but you shouldn't be releasing software with -dirty anyways.
+
+ Exceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += ".post%d" % pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ rendered += plus_or_dot(pieces)
+ rendered += "g%s" % pieces["short"]
+ else:
+ # exception #1
+ rendered = "0.post%d" % pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ rendered += "+g%s" % pieces["short"]
+ return rendered
+
+
+def render_pep440_old(pieces):
+ """TAG[.postDISTANCE[.dev0]] .
+
+ The ".dev0" means dirty.
+
+ Eexceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += ".post%d" % pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ else:
+ # exception #1
+ rendered = "0.post%d" % pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ return rendered
+
+
+def render_git_describe(pieces):
+ """TAG[-DISTANCE-gHEX][-dirty].
+
+ Like 'git describe --tags --dirty --always'.
+
+ Exceptions:
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"]:
+ rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
+ else:
+ # exception #1
+ rendered = pieces["short"]
+ if pieces["dirty"]:
+ rendered += "-dirty"
+ return rendered
+
+
+def render_git_describe_long(pieces):
+ """TAG-DISTANCE-gHEX[-dirty].
+
+ Like 'git describe --tags --dirty --always -long'.
+ The distance/hash is unconditional.
+
+ Exceptions:
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
+ else:
+ # exception #1
+ rendered = pieces["short"]
+ if pieces["dirty"]:
+ rendered += "-dirty"
+ return rendered
+
+
+def render(pieces, style):
+ """Render the given version pieces into the requested style."""
+ if pieces["error"]:
+ return {"version": "unknown",
+ "full-revisionid": pieces.get("long"),
+ "dirty": None,
+ "error": pieces["error"],
+ "date": None}
+
+ if not style or style == "default":
+ style = "pep440" # the default
+
+ if style == "pep440":
+ rendered = render_pep440(pieces)
+ elif style == "pep440-pre":
+ rendered = render_pep440_pre(pieces)
+ elif style == "pep440-post":
+ rendered = render_pep440_post(pieces)
+ elif style == "pep440-old":
+ rendered = render_pep440_old(pieces)
+ elif style == "git-describe":
+ rendered = render_git_describe(pieces)
+ elif style == "git-describe-long":
+ rendered = render_git_describe_long(pieces)
+ else:
+ raise ValueError("unknown style '%s'" % style)
+
+ return {"version": rendered, "full-revisionid": pieces["long"],
+ "dirty": pieces["dirty"], "error": None,
+ "date": pieces.get("date")}
+
+
+def get_versions():
+ """Get version information or return default if unable to do so."""
+ # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+ # __file__, we can work backwards from there to the root. Some
+ # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
+ # case we can only use expanded keywords.
+
+ cfg = get_config()
+ verbose = cfg.verbose
+
+ try:
+ return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
+ verbose)
+ except NotThisMethod:
+ pass
+
+ try:
+ root = os.path.realpath(__file__)
+ # versionfile_source is the relative path from the top of the source
+ # tree (where the .git directory might live) to this file. Invert
+ # this to find the root from __file__.
+ for i in cfg.versionfile_source.split('/'):
+ root = os.path.dirname(root)
+ except NameError:
+ return {"version": "0+unknown", "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to find root of source tree",
+ "date": None}
+
+ try:
+ pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
+ return render(pieces, cfg.style)
+ except NotThisMethod:
+ pass
+
+ try:
+ if cfg.parentdir_prefix:
+ return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
+ except NotThisMethod:
+ pass
+
+ return {"version": "0+unknown", "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to compute version", "date": None}
=====================================
setup.cfg
=====================================
@@ -3,3 +3,20 @@ requires=numpy
release=1
doc_files = doc/Makefile doc/source/*.rst
+[bdist_wheel]
+universal=1
+
+[flake8]
+max-line-length = 120
+
+[versioneer]
+VCS = git
+style = pep440
+versionfile_source = pyorbital/version.py
+versionfile_build =
+tag_prefix = v
+
+[coverage:run]
+omit =
+ pyorbital/version.py
+ versioneer.py
=====================================
setup.py
=====================================
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2011-2014
+# Copyright (c) 2011-2014, 2018
# Author(s):
@@ -22,11 +22,11 @@
from setuptools import setup
import imp
-
-version = imp.load_source('pyorbital.version', 'pyorbital/version.py')
+import versioneer
setup(name='pyorbital',
- version=version.__version__,
+ version=versioneer.get_version(),
+ cmdclass=versioneer.get_cmdclass(),
description='Orbital parameters and astronomical computations in Python',
author='Martin Raspaud, Esben S. Nielsen',
author_email='martin.raspaud at smhi.se',
@@ -38,10 +38,10 @@ setup(name='pyorbital',
"Programming Language :: Python",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Astronomy"],
- url="https://github.com/mraspaud/pyorbital",
+ url="https://github.com/pytroll/pyorbital",
test_suite='pyorbital.tests.suite',
- package_dir = {'pyorbital': 'pyorbital'},
- packages = ['pyorbital'],
- install_requires=['numpy>=1.6.0,!=1.14.0'],
+ package_dir={'pyorbital': 'pyorbital'},
+ packages=['pyorbital'],
+ install_requires=['numpy>=1.11.0,!=1.14.0', 'scipy'],
zip_safe=False,
)
=====================================
versioneer.py
=====================================
@@ -0,0 +1,1822 @@
+
+# Version: 0.18
+
+"""The Versioneer - like a rocketeer, but for versions.
+
+The Versioneer
+==============
+
+* like a rocketeer, but for versions!
+* https://github.com/warner/python-versioneer
+* Brian Warner
+* License: Public Domain
+* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
+* [![Latest Version]
+(https://pypip.in/version/versioneer/badge.svg?style=flat)
+](https://pypi.python.org/pypi/versioneer/)
+* [![Build Status]
+(https://travis-ci.org/warner/python-versioneer.png?branch=master)
+](https://travis-ci.org/warner/python-versioneer)
+
+This is a tool for managing a recorded version number in distutils-based
+python projects. The goal is to remove the tedious and error-prone "update
+the embedded version string" step from your release process. Making a new
+release should be as easy as recording a new tag in your version-control
+system, and maybe making new tarballs.
+
+
+## Quick Install
+
+* `pip install versioneer` to somewhere to your $PATH
+* add a `[versioneer]` section to your setup.cfg (see below)
+* run `versioneer install` in your source tree, commit the results
+
+## Version Identifiers
+
+Source trees come from a variety of places:
+
+* a version-control system checkout (mostly used by developers)
+* a nightly tarball, produced by build automation
+* a snapshot tarball, produced by a web-based VCS browser, like github's
+ "tarball from tag" feature
+* a release tarball, produced by "setup.py sdist", distributed through PyPI
+
+Within each source tree, the version identifier (either a string or a number,
+this tool is format-agnostic) can come from a variety of places:
+
+* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows
+ about recent "tags" and an absolute revision-id
+* the name of the directory into which the tarball was unpacked
+* an expanded VCS keyword ($Id$, etc)
+* a `_version.py` created by some earlier build step
+
+For released software, the version identifier is closely related to a VCS
+tag. Some projects use tag names that include more than just the version
+string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool
+needs to strip the tag prefix to extract the version identifier. For
+unreleased software (between tags), the version identifier should provide
+enough information to help developers recreate the same tree, while also
+giving them an idea of roughly how old the tree is (after version 1.2, before
+version 1.3). Many VCS systems can report a description that captures this,
+for example `git describe --tags --dirty --always` reports things like
+"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
+0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
+uncommitted changes.
+
+The version identifier is used for multiple purposes:
+
+* to allow the module to self-identify its version: `myproject.__version__`
+* to choose a name and prefix for a 'setup.py sdist' tarball
+
+## Theory of Operation
+
+Versioneer works by adding a special `_version.py` file into your source
+tree, where your `__init__.py` can import it. This `_version.py` knows how to
+dynamically ask the VCS tool for version information at import time.
+
+`_version.py` also contains `$Revision$` markers, and the installation
+process marks `_version.py` to have this marker rewritten with a tag name
+during the `git archive` command. As a result, generated tarballs will
+contain enough information to get the proper version.
+
+To allow `setup.py` to compute a version too, a `versioneer.py` is added to
+the top level of your source tree, next to `setup.py` and the `setup.cfg`
+that configures it. This overrides several distutils/setuptools commands to
+compute the version when invoked, and changes `setup.py build` and `setup.py
+sdist` to replace `_version.py` with a small static file that contains just
+the generated version data.
+
+## Installation
+
+See [INSTALL.md](./INSTALL.md) for detailed installation instructions.
+
+## Version-String Flavors
+
+Code which uses Versioneer can learn about its version string at runtime by
+importing `_version` from your main `__init__.py` file and running the
+`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can
+import the top-level `versioneer.py` and run `get_versions()`.
+
+Both functions return a dictionary with different flavors of version
+information:
+
+* `['version']`: A condensed version string, rendered using the selected
+ style. This is the most commonly used value for the project's version
+ string. The default "pep440" style yields strings like `0.11`,
+ `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section
+ below for alternative styles.
+
+* `['full-revisionid']`: detailed revision identifier. For Git, this is the
+ full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".
+
+* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the
+ commit date in ISO 8601 format. This will be None if the date is not
+ available.
+
+* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
+ this is only accurate if run in a VCS checkout, otherwise it is likely to
+ be False or None
+
+* `['error']`: if the version string could not be computed, this will be set
+ to a string describing the problem, otherwise it will be None. It may be
+ useful to throw an exception in setup.py if this is set, to avoid e.g.
+ creating tarballs with a version string of "unknown".
+
+Some variants are more useful than others. Including `full-revisionid` in a
+bug report should allow developers to reconstruct the exact code being tested
+(or indicate the presence of local changes that should be shared with the
+developers). `version` is suitable for display in an "about" box or a CLI
+`--version` output: it can be easily compared against release notes and lists
+of bugs fixed in various releases.
+
+The installer adds the following text to your `__init__.py` to place a basic
+version in `YOURPROJECT.__version__`:
+
+ from ._version import get_versions
+ __version__ = get_versions()['version']
+ del get_versions
+
+## Styles
+
+The setup.cfg `style=` configuration controls how the VCS information is
+rendered into a version string.
+
+The default style, "pep440", produces a PEP440-compliant string, equal to the
+un-prefixed tag name for actual releases, and containing an additional "local
+version" section with more detail for in-between builds. For Git, this is
+TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags
+--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the
+tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and
+that this commit is two revisions ("+2") beyond the "0.11" tag. For released
+software (exactly equal to a known tag), the identifier will only contain the
+stripped tag, e.g. "0.11".
+
+Other styles are available. See [details.md](details.md) in the Versioneer
+source tree for descriptions.
+
+## Debugging
+
+Versioneer tries to avoid fatal errors: if something goes wrong, it will tend
+to return a version of "0+unknown". To investigate the problem, run `setup.py
+version`, which will run the version-lookup code in a verbose mode, and will
+display the full contents of `get_versions()` (including the `error` string,
+which may help identify what went wrong).
+
+## Known Limitations
+
+Some situations are known to cause problems for Versioneer. This details the
+most significant ones. More can be found on Github
+[issues page](https://github.com/warner/python-versioneer/issues).
+
+### Subprojects
+
+Versioneer has limited support for source trees in which `setup.py` is not in
+the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are
+two common reasons why `setup.py` might not be in the root:
+
+* Source trees which contain multiple subprojects, such as
+ [Buildbot](https://github.com/buildbot/buildbot), which contains both
+ "master" and "slave" subprojects, each with their own `setup.py`,
+ `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
+ distributions (and upload multiple independently-installable tarballs).
+* Source trees whose main purpose is to contain a C library, but which also
+ provide bindings to Python (and perhaps other langauges) in subdirectories.
+
+Versioneer will look for `.git` in parent directories, and most operations
+should get the right version string. However `pip` and `setuptools` have bugs
+and implementation details which frequently cause `pip install .` from a
+subproject directory to fail to find a correct version string (so it usually
+defaults to `0+unknown`).
+
+`pip install --editable .` should work correctly. `setup.py install` might
+work too.
+
+Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
+some later version.
+
+[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
+this issue. The discussion in
+[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
+issue from the Versioneer side in more detail.
+[pip PR#3176](https://github.com/pypa/pip/pull/3176) and
+[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
+pip to let Versioneer work correctly.
+
+Versioneer-0.16 and earlier only looked for a `.git` directory next to the
+`setup.cfg`, so subprojects were completely unsupported with those releases.
+
+### Editable installs with setuptools <= 18.5
+
+`setup.py develop` and `pip install --editable .` allow you to install a
+project into a virtualenv once, then continue editing the source code (and
+test) without re-installing after every change.
+
+"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a
+convenient way to specify executable scripts that should be installed along
+with the python package.
+
+These both work as expected when using modern setuptools. When using
+setuptools-18.5 or earlier, however, certain operations will cause
+`pkg_resources.DistributionNotFound` errors when running the entrypoint
+script, which must be resolved by re-installing the package. This happens
+when the install happens with one version, then the egg_info data is
+regenerated while a different version is checked out. Many setup.py commands
+cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
+a different virtualenv), so this can be surprising.
+
+[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
+this one, but upgrading to a newer version of setuptools should probably
+resolve it.
+
+### Unicode version strings
+
+While Versioneer works (and is continually tested) with both Python 2 and
+Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
+Newer releases probably generate unicode version strings on py2. It's not
+clear that this is wrong, but it may be surprising for applications when then
+write these strings to a network connection or include them in bytes-oriented
+APIs like cryptographic checksums.
+
+[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
+this question.
+
+
+## Updating Versioneer
+
+To upgrade your project to a new release of Versioneer, do the following:
+
+* install the new Versioneer (`pip install -U versioneer` or equivalent)
+* edit `setup.cfg`, if necessary, to include any new configuration settings
+ indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
+* re-run `versioneer install` in your source tree, to replace
+ `SRC/_version.py`
+* commit any changed files
+
+## Future Directions
+
+This tool is designed to make it easily extended to other version-control
+systems: all VCS-specific components are in separate directories like
+src/git/ . The top-level `versioneer.py` script is assembled from these
+components by running make-versioneer.py . In the future, make-versioneer.py
+will take a VCS name as an argument, and will construct a version of
+`versioneer.py` that is specific to the given VCS. It might also take the
+configuration arguments that are currently provided manually during
+installation by editing setup.py . Alternatively, it might go the other
+direction and include code from all supported VCS systems, reducing the
+number of intermediate scripts.
+
+
+## License
+
+To make Versioneer easier to embed, all its code is dedicated to the public
+domain. The `_version.py` that it creates is also in the public domain.
+Specifically, both are released under the Creative Commons "Public Domain
+Dedication" license (CC0-1.0), as described in
+https://creativecommons.org/publicdomain/zero/1.0/ .
+
+"""
+
+from __future__ import print_function
+try:
+ import configparser
+except ImportError:
+ import ConfigParser as configparser
+import errno
+import json
+import os
+import re
+import subprocess
+import sys
+
+
+class VersioneerConfig:
+ """Container for Versioneer configuration parameters."""
+
+
+def get_root():
+ """Get the project root directory.
+
+ We require that all commands are run from the project root, i.e. the
+ directory that contains setup.py, setup.cfg, and versioneer.py .
+ """
+ root = os.path.realpath(os.path.abspath(os.getcwd()))
+ setup_py = os.path.join(root, "setup.py")
+ versioneer_py = os.path.join(root, "versioneer.py")
+ if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
+ # allow 'python path/to/setup.py COMMAND'
+ root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
+ setup_py = os.path.join(root, "setup.py")
+ versioneer_py = os.path.join(root, "versioneer.py")
+ if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
+ err = ("Versioneer was unable to run the project root directory. "
+ "Versioneer requires setup.py to be executed from "
+ "its immediate directory (like 'python setup.py COMMAND'), "
+ "or in a way that lets it use sys.argv[0] to find the root "
+ "(like 'python path/to/setup.py COMMAND').")
+ raise VersioneerBadRootError(err)
+ try:
+ # Certain runtime workflows (setup.py install/develop in a setuptools
+ # tree) execute all dependencies in a single python process, so
+ # "versioneer" may be imported multiple times, and python's shared
+ # module-import table will cache the first one. So we can't use
+ # os.path.dirname(__file__), as that will find whichever
+ # versioneer.py was first imported, even in later projects.
+ me = os.path.realpath(os.path.abspath(__file__))
+ me_dir = os.path.normcase(os.path.splitext(me)[0])
+ vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
+ if me_dir != vsr_dir:
+ print("Warning: build in %s is using versioneer.py from %s"
+ % (os.path.dirname(me), versioneer_py))
+ except NameError:
+ pass
+ return root
+
+
+def get_config_from_root(root):
+ """Read the project setup.cfg file to determine Versioneer config."""
+ # This might raise EnvironmentError (if setup.cfg is missing), or
+ # configparser.NoSectionError (if it lacks a [versioneer] section), or
+ # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
+ # the top of versioneer.py for instructions on writing your setup.cfg .
+ setup_cfg = os.path.join(root, "setup.cfg")
+ parser = configparser.SafeConfigParser()
+ with open(setup_cfg, "r") as f:
+ parser.readfp(f)
+ VCS = parser.get("versioneer", "VCS") # mandatory
+
+ def get(parser, name):
+ if parser.has_option("versioneer", name):
+ return parser.get("versioneer", name)
+ return None
+ cfg = VersioneerConfig()
+ cfg.VCS = VCS
+ cfg.style = get(parser, "style") or ""
+ cfg.versionfile_source = get(parser, "versionfile_source")
+ cfg.versionfile_build = get(parser, "versionfile_build")
+ cfg.tag_prefix = get(parser, "tag_prefix")
+ if cfg.tag_prefix in ("''", '""'):
+ cfg.tag_prefix = ""
+ cfg.parentdir_prefix = get(parser, "parentdir_prefix")
+ cfg.verbose = get(parser, "verbose")
+ return cfg
+
+
+class NotThisMethod(Exception):
+ """Exception raised if a method is not valid for the current scenario."""
+
+
+# these dictionaries contain VCS-specific tools
+LONG_VERSION_PY = {}
+HANDLERS = {}
+
+
+def register_vcs_handler(vcs, method): # decorator
+ """Decorator to mark a method as the handler for a particular VCS."""
+ def decorate(f):
+ """Store f in HANDLERS[vcs][method]."""
+ if vcs not in HANDLERS:
+ HANDLERS[vcs] = {}
+ HANDLERS[vcs][method] = f
+ return f
+ return decorate
+
+
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+ env=None):
+ """Call the given command(s)."""
+ assert isinstance(commands, list)
+ p = None
+ for c in commands:
+ try:
+ dispcmd = str([c] + args)
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+ stdout=subprocess.PIPE,
+ stderr=(subprocess.PIPE if hide_stderr
+ else None))
+ break
+ except EnvironmentError:
+ e = sys.exc_info()[1]
+ if e.errno == errno.ENOENT:
+ continue
+ if verbose:
+ print("unable to run %s" % dispcmd)
+ print(e)
+ return None, None
+ else:
+ if verbose:
+ print("unable to find command, tried %s" % (commands,))
+ return None, None
+ stdout = p.communicate()[0].strip()
+ if sys.version_info[0] >= 3:
+ stdout = stdout.decode()
+ if p.returncode != 0:
+ if verbose:
+ print("unable to run %s (error)" % dispcmd)
+ print("stdout was %s" % stdout)
+ return None, p.returncode
+ return stdout, p.returncode
+
+
+LONG_VERSION_PY['git'] = '''
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by githubs download-from-tag
+# feature). Distribution tarballs (built by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
+
+# This file is released into the public domain. Generated by
+# versioneer-0.18 (https://github.com/warner/python-versioneer)
+
+"""Git implementation of _version.py."""
+
+import errno
+import os
+import re
+import subprocess
+import sys
+
+
+def get_keywords():
+ """Get the keywords needed to look up the version information."""
+ # these strings will be replaced by git during git-archive.
+ # setup.py/versioneer.py will grep for the variable names, so they must
+ # each be defined on a line of their own. _version.py will just call
+ # get_keywords().
+ git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
+ git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
+ git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s"
+ keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
+ return keywords
+
+
+class VersioneerConfig:
+ """Container for Versioneer configuration parameters."""
+
+
+def get_config():
+ """Create, populate and return the VersioneerConfig() object."""
+ # these strings are filled in when 'setup.py versioneer' creates
+ # _version.py
+ cfg = VersioneerConfig()
+ cfg.VCS = "git"
+ cfg.style = "%(STYLE)s"
+ cfg.tag_prefix = "%(TAG_PREFIX)s"
+ cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s"
+ cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s"
+ cfg.verbose = False
+ return cfg
+
+
+class NotThisMethod(Exception):
+ """Exception raised if a method is not valid for the current scenario."""
+
+
+LONG_VERSION_PY = {}
+HANDLERS = {}
+
+
+def register_vcs_handler(vcs, method): # decorator
+ """Decorator to mark a method as the handler for a particular VCS."""
+ def decorate(f):
+ """Store f in HANDLERS[vcs][method]."""
+ if vcs not in HANDLERS:
+ HANDLERS[vcs] = {}
+ HANDLERS[vcs][method] = f
+ return f
+ return decorate
+
+
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+ env=None):
+ """Call the given command(s)."""
+ assert isinstance(commands, list)
+ p = None
+ for c in commands:
+ try:
+ dispcmd = str([c] + args)
+ # remember shell=False, so use git.cmd on windows, not just git
+ p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+ stdout=subprocess.PIPE,
+ stderr=(subprocess.PIPE if hide_stderr
+ else None))
+ break
+ except EnvironmentError:
+ e = sys.exc_info()[1]
+ if e.errno == errno.ENOENT:
+ continue
+ if verbose:
+ print("unable to run %%s" %% dispcmd)
+ print(e)
+ return None, None
+ else:
+ if verbose:
+ print("unable to find command, tried %%s" %% (commands,))
+ return None, None
+ stdout = p.communicate()[0].strip()
+ if sys.version_info[0] >= 3:
+ stdout = stdout.decode()
+ if p.returncode != 0:
+ if verbose:
+ print("unable to run %%s (error)" %% dispcmd)
+ print("stdout was %%s" %% stdout)
+ return None, p.returncode
+ return stdout, p.returncode
+
+
+def versions_from_parentdir(parentdir_prefix, root, verbose):
+ """Try to determine the version from the parent directory name.
+
+ Source tarballs conventionally unpack into a directory that includes both
+ the project name and a version string. We will also support searching up
+ two directory levels for an appropriately named parent directory
+ """
+ rootdirs = []
+
+ for i in range(3):
+ dirname = os.path.basename(root)
+ if dirname.startswith(parentdir_prefix):
+ return {"version": dirname[len(parentdir_prefix):],
+ "full-revisionid": None,
+ "dirty": False, "error": None, "date": None}
+ else:
+ rootdirs.append(root)
+ root = os.path.dirname(root) # up a level
+
+ if verbose:
+ print("Tried directories %%s but none started with prefix %%s" %%
+ (str(rootdirs), parentdir_prefix))
+ raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+
+
+ at register_vcs_handler("git", "get_keywords")
+def git_get_keywords(versionfile_abs):
+ """Extract version information from the given file."""
+ # the code embedded in _version.py can just fetch the value of these
+ # keywords. When used from setup.py, we don't want to import _version.py,
+ # so we do it with a regexp instead. This function is not used from
+ # _version.py.
+ keywords = {}
+ try:
+ f = open(versionfile_abs, "r")
+ for line in f.readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["full"] = mo.group(1)
+ if line.strip().startswith("git_date ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["date"] = mo.group(1)
+ f.close()
+ except EnvironmentError:
+ pass
+ return keywords
+
+
+ at register_vcs_handler("git", "keywords")
+def git_versions_from_keywords(keywords, tag_prefix, verbose):
+ """Get version information from git keywords."""
+ if not keywords:
+ raise NotThisMethod("no keywords at all, weird")
+ date = keywords.get("date")
+ if date is not None:
+ # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
+ # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
+ # -like" string, which we must then edit to make compliant), because
+ # it's been around since git-1.5.3, and it's too difficult to
+ # discover which version we're using, or to work around using an
+ # older one.
+ date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ refnames = keywords["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print("keywords are unexpanded, not using")
+ raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+ # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+ TAG = "tag: "
+ tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
+ if not tags:
+ # Either we're using git < 1.8.3, or there really are no tags. We use
+ # a heuristic: assume all version tags have a digit. The old git %%d
+ # expansion behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us distinguish
+ # between branches and tags. By ignoring refnames without digits, we
+ # filter out many common branch names like "release" and
+ # "stabilization", as well as "HEAD" and "master".
+ tags = set([r for r in refs if re.search(r'\d', r)])
+ if verbose:
+ print("discarding '%%s', no digits" %% ",".join(refs - tags))
+ if verbose:
+ print("likely tags: %%s" %% ",".join(sorted(tags)))
+ for ref in sorted(tags):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print("picking %%s" %% r)
+ return {"version": r,
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False, "error": None,
+ "date": date}
+ # no suitable tags, so version is "0+unknown", but full hex is still there
+ if verbose:
+ print("no suitable tags, using unknown + full revision id")
+ return {"version": "0+unknown",
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False, "error": "no suitable tags", "date": None}
+
+
+ at register_vcs_handler("git", "pieces_from_vcs")
+def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ """Get version from 'git describe' in the root of the source tree.
+
+ This only gets called if the git-archive 'subst' keywords were *not*
+ expanded, and _version.py hasn't already been rewritten with a short
+ version string, meaning we're inside a checked out source tree.
+ """
+ GITS = ["git"]
+ if sys.platform == "win32":
+ GITS = ["git.cmd", "git.exe"]
+
+ out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+ hide_stderr=True)
+ if rc != 0:
+ if verbose:
+ print("Directory %%s not under git control" %% root)
+ raise NotThisMethod("'git rev-parse --git-dir' returned error")
+
+ # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+ # if there isn't one, this yields HEX[-dirty] (no NUM)
+ describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+ "--always", "--long",
+ "--match", "%%s*" %% tag_prefix],
+ cwd=root)
+ # --long was added in git-1.5.5
+ if describe_out is None:
+ raise NotThisMethod("'git describe' failed")
+ describe_out = describe_out.strip()
+ full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
+ if full_out is None:
+ raise NotThisMethod("'git rev-parse' failed")
+ full_out = full_out.strip()
+
+ pieces = {}
+ pieces["long"] = full_out
+ pieces["short"] = full_out[:7] # maybe improved later
+ pieces["error"] = None
+
+ # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+ # TAG might have hyphens.
+ git_describe = describe_out
+
+ # look for -dirty suffix
+ dirty = git_describe.endswith("-dirty")
+ pieces["dirty"] = dirty
+ if dirty:
+ git_describe = git_describe[:git_describe.rindex("-dirty")]
+
+ # now we have TAG-NUM-gHEX or HEX
+
+ if "-" in git_describe:
+ # TAG-NUM-gHEX
+ mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+ if not mo:
+ # unparseable. Maybe git-describe is misbehaving?
+ pieces["error"] = ("unable to parse git-describe output: '%%s'"
+ %% describe_out)
+ return pieces
+
+ # tag
+ full_tag = mo.group(1)
+ if not full_tag.startswith(tag_prefix):
+ if verbose:
+ fmt = "tag '%%s' doesn't start with prefix '%%s'"
+ print(fmt %% (full_tag, tag_prefix))
+ pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'"
+ %% (full_tag, tag_prefix))
+ return pieces
+ pieces["closest-tag"] = full_tag[len(tag_prefix):]
+
+ # distance: number of commits since tag
+ pieces["distance"] = int(mo.group(2))
+
+ # commit: short hex revision ID
+ pieces["short"] = mo.group(3)
+
+ else:
+ # HEX: no tags
+ pieces["closest-tag"] = None
+ count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+ cwd=root)
+ pieces["distance"] = int(count_out) # total number of commits
+
+ # commit date: see ISO-8601 comment in git_versions_from_keywords()
+ date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
+ cwd=root)[0].strip()
+ pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+
+ return pieces
+
+
+def plus_or_dot(pieces):
+ """Return a + if we don't already have one, else return a ."""
+ if "+" in pieces.get("closest-tag", ""):
+ return "."
+ return "+"
+
+
+def render_pep440(pieces):
+ """Build up version string, with post-release "local version identifier".
+
+ Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+ get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
+
+ Exceptions:
+ 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += plus_or_dot(pieces)
+ rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ else:
+ # exception #1
+ rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"],
+ pieces["short"])
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ return rendered
+
+
+def render_pep440_pre(pieces):
+ """TAG[.post.devDISTANCE] -- No -dirty.
+
+ Exceptions:
+ 1: no tags. 0.post.devDISTANCE
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"]:
+ rendered += ".post.dev%%d" %% pieces["distance"]
+ else:
+ # exception #1
+ rendered = "0.post.dev%%d" %% pieces["distance"]
+ return rendered
+
+
+def render_pep440_post(pieces):
+ """TAG[.postDISTANCE[.dev0]+gHEX] .
+
+ The ".dev0" means dirty. Note that .dev0 sorts backwards
+ (a dirty tree will appear "older" than the corresponding clean one),
+ but you shouldn't be releasing software with -dirty anyways.
+
+ Exceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += ".post%%d" %% pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ rendered += plus_or_dot(pieces)
+ rendered += "g%%s" %% pieces["short"]
+ else:
+ # exception #1
+ rendered = "0.post%%d" %% pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ rendered += "+g%%s" %% pieces["short"]
+ return rendered
+
+
+def render_pep440_old(pieces):
+ """TAG[.postDISTANCE[.dev0]] .
+
+ The ".dev0" means dirty.
+
+ Eexceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += ".post%%d" %% pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ else:
+ # exception #1
+ rendered = "0.post%%d" %% pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ return rendered
+
+
+def render_git_describe(pieces):
+ """TAG[-DISTANCE-gHEX][-dirty].
+
+ Like 'git describe --tags --dirty --always'.
+
+ Exceptions:
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"]:
+ rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
+ else:
+ # exception #1
+ rendered = pieces["short"]
+ if pieces["dirty"]:
+ rendered += "-dirty"
+ return rendered
+
+
+def render_git_describe_long(pieces):
+ """TAG-DISTANCE-gHEX[-dirty].
+
+ Like 'git describe --tags --dirty --always -long'.
+ The distance/hash is unconditional.
+
+ Exceptions:
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
+ else:
+ # exception #1
+ rendered = pieces["short"]
+ if pieces["dirty"]:
+ rendered += "-dirty"
+ return rendered
+
+
+def render(pieces, style):
+ """Render the given version pieces into the requested style."""
+ if pieces["error"]:
+ return {"version": "unknown",
+ "full-revisionid": pieces.get("long"),
+ "dirty": None,
+ "error": pieces["error"],
+ "date": None}
+
+ if not style or style == "default":
+ style = "pep440" # the default
+
+ if style == "pep440":
+ rendered = render_pep440(pieces)
+ elif style == "pep440-pre":
+ rendered = render_pep440_pre(pieces)
+ elif style == "pep440-post":
+ rendered = render_pep440_post(pieces)
+ elif style == "pep440-old":
+ rendered = render_pep440_old(pieces)
+ elif style == "git-describe":
+ rendered = render_git_describe(pieces)
+ elif style == "git-describe-long":
+ rendered = render_git_describe_long(pieces)
+ else:
+ raise ValueError("unknown style '%%s'" %% style)
+
+ return {"version": rendered, "full-revisionid": pieces["long"],
+ "dirty": pieces["dirty"], "error": None,
+ "date": pieces.get("date")}
+
+
+def get_versions():
+ """Get version information or return default if unable to do so."""
+ # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+ # __file__, we can work backwards from there to the root. Some
+ # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
+ # case we can only use expanded keywords.
+
+ cfg = get_config()
+ verbose = cfg.verbose
+
+ try:
+ return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
+ verbose)
+ except NotThisMethod:
+ pass
+
+ try:
+ root = os.path.realpath(__file__)
+ # versionfile_source is the relative path from the top of the source
+ # tree (where the .git directory might live) to this file. Invert
+ # this to find the root from __file__.
+ for i in cfg.versionfile_source.split('/'):
+ root = os.path.dirname(root)
+ except NameError:
+ return {"version": "0+unknown", "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to find root of source tree",
+ "date": None}
+
+ try:
+ pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
+ return render(pieces, cfg.style)
+ except NotThisMethod:
+ pass
+
+ try:
+ if cfg.parentdir_prefix:
+ return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
+ except NotThisMethod:
+ pass
+
+ return {"version": "0+unknown", "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to compute version", "date": None}
+'''
+
+
+ at register_vcs_handler("git", "get_keywords")
+def git_get_keywords(versionfile_abs):
+ """Extract version information from the given file."""
+ # the code embedded in _version.py can just fetch the value of these
+ # keywords. When used from setup.py, we don't want to import _version.py,
+ # so we do it with a regexp instead. This function is not used from
+ # _version.py.
+ keywords = {}
+ try:
+ f = open(versionfile_abs, "r")
+ for line in f.readlines():
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["full"] = mo.group(1)
+ if line.strip().startswith("git_date ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["date"] = mo.group(1)
+ f.close()
+ except EnvironmentError:
+ pass
+ return keywords
+
+
+ at register_vcs_handler("git", "keywords")
+def git_versions_from_keywords(keywords, tag_prefix, verbose):
+ """Get version information from git keywords."""
+ if not keywords:
+ raise NotThisMethod("no keywords at all, weird")
+ date = keywords.get("date")
+ if date is not None:
+ # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
+ # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
+ # -like" string, which we must then edit to make compliant), because
+ # it's been around since git-1.5.3, and it's too difficult to
+ # discover which version we're using, or to work around using an
+ # older one.
+ date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ refnames = keywords["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print("keywords are unexpanded, not using")
+ raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+ refs = set([r.strip() for r in refnames.strip("()").split(",")])
+ # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+ # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+ TAG = "tag: "
+ tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
+ if not tags:
+ # Either we're using git < 1.8.3, or there really are no tags. We use
+ # a heuristic: assume all version tags have a digit. The old git %d
+ # expansion behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us distinguish
+ # between branches and tags. By ignoring refnames without digits, we
+ # filter out many common branch names like "release" and
+ # "stabilization", as well as "HEAD" and "master".
+ tags = set([r for r in refs if re.search(r'\d', r)])
+ if verbose:
+ print("discarding '%s', no digits" % ",".join(refs - tags))
+ if verbose:
+ print("likely tags: %s" % ",".join(sorted(tags)))
+ for ref in sorted(tags):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix):]
+ if verbose:
+ print("picking %s" % r)
+ return {"version": r,
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False, "error": None,
+ "date": date}
+ # no suitable tags, so version is "0+unknown", but full hex is still there
+ if verbose:
+ print("no suitable tags, using unknown + full revision id")
+ return {"version": "0+unknown",
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False, "error": "no suitable tags", "date": None}
+
+
+ at register_vcs_handler("git", "pieces_from_vcs")
+def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ """Get version from 'git describe' in the root of the source tree.
+
+ This only gets called if the git-archive 'subst' keywords were *not*
+ expanded, and _version.py hasn't already been rewritten with a short
+ version string, meaning we're inside a checked out source tree.
+ """
+ GITS = ["git"]
+ if sys.platform == "win32":
+ GITS = ["git.cmd", "git.exe"]
+
+ out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+ hide_stderr=True)
+ if rc != 0:
+ if verbose:
+ print("Directory %s not under git control" % root)
+ raise NotThisMethod("'git rev-parse --git-dir' returned error")
+
+ # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+ # if there isn't one, this yields HEX[-dirty] (no NUM)
+ describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+ "--always", "--long",
+ "--match", "%s*" % tag_prefix],
+ cwd=root)
+ # --long was added in git-1.5.5
+ if describe_out is None:
+ raise NotThisMethod("'git describe' failed")
+ describe_out = describe_out.strip()
+ full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
+ if full_out is None:
+ raise NotThisMethod("'git rev-parse' failed")
+ full_out = full_out.strip()
+
+ pieces = {}
+ pieces["long"] = full_out
+ pieces["short"] = full_out[:7] # maybe improved later
+ pieces["error"] = None
+
+ # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+ # TAG might have hyphens.
+ git_describe = describe_out
+
+ # look for -dirty suffix
+ dirty = git_describe.endswith("-dirty")
+ pieces["dirty"] = dirty
+ if dirty:
+ git_describe = git_describe[:git_describe.rindex("-dirty")]
+
+ # now we have TAG-NUM-gHEX or HEX
+
+ if "-" in git_describe:
+ # TAG-NUM-gHEX
+ mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+ if not mo:
+ # unparseable. Maybe git-describe is misbehaving?
+ pieces["error"] = ("unable to parse git-describe output: '%s'"
+ % describe_out)
+ return pieces
+
+ # tag
+ full_tag = mo.group(1)
+ if not full_tag.startswith(tag_prefix):
+ if verbose:
+ fmt = "tag '%s' doesn't start with prefix '%s'"
+ print(fmt % (full_tag, tag_prefix))
+ pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
+ % (full_tag, tag_prefix))
+ return pieces
+ pieces["closest-tag"] = full_tag[len(tag_prefix):]
+
+ # distance: number of commits since tag
+ pieces["distance"] = int(mo.group(2))
+
+ # commit: short hex revision ID
+ pieces["short"] = mo.group(3)
+
+ else:
+ # HEX: no tags
+ pieces["closest-tag"] = None
+ count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+ cwd=root)
+ pieces["distance"] = int(count_out) # total number of commits
+
+ # commit date: see ISO-8601 comment in git_versions_from_keywords()
+ date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
+ cwd=root)[0].strip()
+ pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+
+ return pieces
+
+
+def do_vcs_install(manifest_in, versionfile_source, ipy):
+ """Git-specific installation logic for Versioneer.
+
+ For Git, this means creating/changing .gitattributes to mark _version.py
+ for export-subst keyword substitution.
+ """
+ GITS = ["git"]
+ if sys.platform == "win32":
+ GITS = ["git.cmd", "git.exe"]
+ files = [manifest_in, versionfile_source]
+ if ipy:
+ files.append(ipy)
+ try:
+ me = __file__
+ if me.endswith(".pyc") or me.endswith(".pyo"):
+ me = os.path.splitext(me)[0] + ".py"
+ versioneer_file = os.path.relpath(me)
+ except NameError:
+ versioneer_file = "versioneer.py"
+ files.append(versioneer_file)
+ present = False
+ try:
+ f = open(".gitattributes", "r")
+ for line in f.readlines():
+ if line.strip().startswith(versionfile_source):
+ if "export-subst" in line.strip().split()[1:]:
+ present = True
+ f.close()
+ except EnvironmentError:
+ pass
+ if not present:
+ f = open(".gitattributes", "a+")
+ f.write("%s export-subst\n" % versionfile_source)
+ f.close()
+ files.append(".gitattributes")
+ run_command(GITS, ["add", "--"] + files)
+
+
+def versions_from_parentdir(parentdir_prefix, root, verbose):
+ """Try to determine the version from the parent directory name.
+
+ Source tarballs conventionally unpack into a directory that includes both
+ the project name and a version string. We will also support searching up
+ two directory levels for an appropriately named parent directory
+ """
+ rootdirs = []
+
+ for i in range(3):
+ dirname = os.path.basename(root)
+ if dirname.startswith(parentdir_prefix):
+ return {"version": dirname[len(parentdir_prefix):],
+ "full-revisionid": None,
+ "dirty": False, "error": None, "date": None}
+ else:
+ rootdirs.append(root)
+ root = os.path.dirname(root) # up a level
+
+ if verbose:
+ print("Tried directories %s but none started with prefix %s" %
+ (str(rootdirs), parentdir_prefix))
+ raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+
+
+SHORT_VERSION_PY = """
+# This file was generated by 'versioneer.py' (0.18) from
+# revision-control system data, or from the parent directory name of an
+# unpacked source archive. Distribution tarballs contain a pre-generated copy
+# of this file.
+
+import json
+
+version_json = '''
+%s
+''' # END VERSION_JSON
+
+
+def get_versions():
+ return json.loads(version_json)
+"""
+
+
+def versions_from_file(filename):
+ """Try to determine the version from _version.py if present."""
+ try:
+ with open(filename) as f:
+ contents = f.read()
+ except EnvironmentError:
+ raise NotThisMethod("unable to read _version.py")
+ mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON",
+ contents, re.M | re.S)
+ if not mo:
+ mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON",
+ contents, re.M | re.S)
+ if not mo:
+ raise NotThisMethod("no version_json in _version.py")
+ return json.loads(mo.group(1))
+
+
+def write_to_version_file(filename, versions):
+ """Write the given version number to the given _version.py file."""
+ os.unlink(filename)
+ contents = json.dumps(versions, sort_keys=True,
+ indent=1, separators=(",", ": "))
+ with open(filename, "w") as f:
+ f.write(SHORT_VERSION_PY % contents)
+
+ print("set %s to '%s'" % (filename, versions["version"]))
+
+
+def plus_or_dot(pieces):
+ """Return a + if we don't already have one, else return a ."""
+ if "+" in pieces.get("closest-tag", ""):
+ return "."
+ return "+"
+
+
+def render_pep440(pieces):
+ """Build up version string, with post-release "local version identifier".
+
+ Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+ get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
+
+ Exceptions:
+ 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += plus_or_dot(pieces)
+ rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ else:
+ # exception #1
+ rendered = "0+untagged.%d.g%s" % (pieces["distance"],
+ pieces["short"])
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ return rendered
+
+
+def render_pep440_pre(pieces):
+ """TAG[.post.devDISTANCE] -- No -dirty.
+
+ Exceptions:
+ 1: no tags. 0.post.devDISTANCE
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"]:
+ rendered += ".post.dev%d" % pieces["distance"]
+ else:
+ # exception #1
+ rendered = "0.post.dev%d" % pieces["distance"]
+ return rendered
+
+
+def render_pep440_post(pieces):
+ """TAG[.postDISTANCE[.dev0]+gHEX] .
+
+ The ".dev0" means dirty. Note that .dev0 sorts backwards
+ (a dirty tree will appear "older" than the corresponding clean one),
+ but you shouldn't be releasing software with -dirty anyways.
+
+ Exceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += ".post%d" % pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ rendered += plus_or_dot(pieces)
+ rendered += "g%s" % pieces["short"]
+ else:
+ # exception #1
+ rendered = "0.post%d" % pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ rendered += "+g%s" % pieces["short"]
+ return rendered
+
+
+def render_pep440_old(pieces):
+ """TAG[.postDISTANCE[.dev0]] .
+
+ The ".dev0" means dirty.
+
+ Eexceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += ".post%d" % pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ else:
+ # exception #1
+ rendered = "0.post%d" % pieces["distance"]
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ return rendered
+
+
+def render_git_describe(pieces):
+ """TAG[-DISTANCE-gHEX][-dirty].
+
+ Like 'git describe --tags --dirty --always'.
+
+ Exceptions:
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"]:
+ rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
+ else:
+ # exception #1
+ rendered = pieces["short"]
+ if pieces["dirty"]:
+ rendered += "-dirty"
+ return rendered
+
+
+def render_git_describe_long(pieces):
+ """TAG-DISTANCE-gHEX[-dirty].
+
+ Like 'git describe --tags --dirty --always -long'.
+ The distance/hash is unconditional.
+
+ Exceptions:
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
+ else:
+ # exception #1
+ rendered = pieces["short"]
+ if pieces["dirty"]:
+ rendered += "-dirty"
+ return rendered
+
+
+def render(pieces, style):
+ """Render the given version pieces into the requested style."""
+ if pieces["error"]:
+ return {"version": "unknown",
+ "full-revisionid": pieces.get("long"),
+ "dirty": None,
+ "error": pieces["error"],
+ "date": None}
+
+ if not style or style == "default":
+ style = "pep440" # the default
+
+ if style == "pep440":
+ rendered = render_pep440(pieces)
+ elif style == "pep440-pre":
+ rendered = render_pep440_pre(pieces)
+ elif style == "pep440-post":
+ rendered = render_pep440_post(pieces)
+ elif style == "pep440-old":
+ rendered = render_pep440_old(pieces)
+ elif style == "git-describe":
+ rendered = render_git_describe(pieces)
+ elif style == "git-describe-long":
+ rendered = render_git_describe_long(pieces)
+ else:
+ raise ValueError("unknown style '%s'" % style)
+
+ return {"version": rendered, "full-revisionid": pieces["long"],
+ "dirty": pieces["dirty"], "error": None,
+ "date": pieces.get("date")}
+
+
+class VersioneerBadRootError(Exception):
+ """The project root directory is unknown or missing key files."""
+
+
+def get_versions(verbose=False):
+ """Get the project version from whatever source is available.
+
+ Returns dict with two keys: 'version' and 'full'.
+ """
+ if "versioneer" in sys.modules:
+ # see the discussion in cmdclass.py:get_cmdclass()
+ del sys.modules["versioneer"]
+
+ root = get_root()
+ cfg = get_config_from_root(root)
+
+ assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
+ handlers = HANDLERS.get(cfg.VCS)
+ assert handlers, "unrecognized VCS '%s'" % cfg.VCS
+ verbose = verbose or cfg.verbose
+ assert cfg.versionfile_source is not None, \
+ "please set versioneer.versionfile_source"
+ assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
+
+ versionfile_abs = os.path.join(root, cfg.versionfile_source)
+
+ # extract version from first of: _version.py, VCS command (e.g. 'git
+ # describe'), parentdir. This is meant to work for developers using a
+ # source checkout, for users of a tarball created by 'setup.py sdist',
+ # and for users of a tarball/zipball created by 'git archive' or github's
+ # download-from-tag feature or the equivalent in other VCSes.
+
+ get_keywords_f = handlers.get("get_keywords")
+ from_keywords_f = handlers.get("keywords")
+ if get_keywords_f and from_keywords_f:
+ try:
+ keywords = get_keywords_f(versionfile_abs)
+ ver = from_keywords_f(keywords, cfg.tag_prefix, verbose)
+ if verbose:
+ print("got version from expanded keyword %s" % ver)
+ return ver
+ except NotThisMethod:
+ pass
+
+ try:
+ ver = versions_from_file(versionfile_abs)
+ if verbose:
+ print("got version from file %s %s" % (versionfile_abs, ver))
+ return ver
+ except NotThisMethod:
+ pass
+
+ from_vcs_f = handlers.get("pieces_from_vcs")
+ if from_vcs_f:
+ try:
+ pieces = from_vcs_f(cfg.tag_prefix, root, verbose)
+ ver = render(pieces, cfg.style)
+ if verbose:
+ print("got version from VCS %s" % ver)
+ return ver
+ except NotThisMethod:
+ pass
+
+ try:
+ if cfg.parentdir_prefix:
+ ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
+ if verbose:
+ print("got version from parentdir %s" % ver)
+ return ver
+ except NotThisMethod:
+ pass
+
+ if verbose:
+ print("unable to compute version")
+
+ return {"version": "0+unknown", "full-revisionid": None,
+ "dirty": None, "error": "unable to compute version",
+ "date": None}
+
+
+def get_version():
+ """Get the short version string for this project."""
+ return get_versions()["version"]
+
+
+def get_cmdclass():
+ """Get the custom setuptools/distutils subclasses used by Versioneer."""
+ if "versioneer" in sys.modules:
+ del sys.modules["versioneer"]
+ # this fixes the "python setup.py develop" case (also 'install' and
+ # 'easy_install .'), in which subdependencies of the main project are
+ # built (using setup.py bdist_egg) in the same python process. Assume
+ # a main project A and a dependency B, which use different versions
+ # of Versioneer. A's setup.py imports A's Versioneer, leaving it in
+ # sys.modules by the time B's setup.py is executed, causing B to run
+ # with the wrong versioneer. Setuptools wraps the sub-dep builds in a
+ # sandbox that restores sys.modules to it's pre-build state, so the
+ # parent is protected against the child's "import versioneer". By
+ # removing ourselves from sys.modules here, before the child build
+ # happens, we protect the child from the parent's versioneer too.
+ # Also see https://github.com/warner/python-versioneer/issues/52
+
+ cmds = {}
+
+ # we add "version" to both distutils and setuptools
+ from distutils.core import Command
+
+ class cmd_version(Command):
+ description = "report generated version string"
+ user_options = []
+ boolean_options = []
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ vers = get_versions(verbose=True)
+ print("Version: %s" % vers["version"])
+ print(" full-revisionid: %s" % vers.get("full-revisionid"))
+ print(" dirty: %s" % vers.get("dirty"))
+ print(" date: %s" % vers.get("date"))
+ if vers["error"]:
+ print(" error: %s" % vers["error"])
+ cmds["version"] = cmd_version
+
+ # we override "build_py" in both distutils and setuptools
+ #
+ # most invocation pathways end up running build_py:
+ # distutils/build -> build_py
+ # distutils/install -> distutils/build ->..
+ # setuptools/bdist_wheel -> distutils/install ->..
+ # setuptools/bdist_egg -> distutils/install_lib -> build_py
+ # setuptools/install -> bdist_egg ->..
+ # setuptools/develop -> ?
+ # pip install:
+ # copies source tree to a tempdir before running egg_info/etc
+ # if .git isn't copied too, 'git describe' will fail
+ # then does setup.py bdist_wheel, or sometimes setup.py install
+ # setup.py egg_info -> ?
+
+ # we override different "build_py" commands for both environments
+ if "setuptools" in sys.modules:
+ from setuptools.command.build_py import build_py as _build_py
+ else:
+ from distutils.command.build_py import build_py as _build_py
+
+ class cmd_build_py(_build_py):
+ def run(self):
+ root = get_root()
+ cfg = get_config_from_root(root)
+ versions = get_versions()
+ _build_py.run(self)
+ # now locate _version.py in the new build/ directory and replace
+ # it with an updated value
+ if cfg.versionfile_build:
+ target_versionfile = os.path.join(self.build_lib,
+ cfg.versionfile_build)
+ print("UPDATING %s" % target_versionfile)
+ write_to_version_file(target_versionfile, versions)
+ cmds["build_py"] = cmd_build_py
+
+ if "cx_Freeze" in sys.modules: # cx_freeze enabled?
+ from cx_Freeze.dist import build_exe as _build_exe
+ # nczeczulin reports that py2exe won't like the pep440-style string
+ # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
+ # setup(console=[{
+ # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION
+ # "product_version": versioneer.get_version(),
+ # ...
+
+ class cmd_build_exe(_build_exe):
+ def run(self):
+ root = get_root()
+ cfg = get_config_from_root(root)
+ versions = get_versions()
+ target_versionfile = cfg.versionfile_source
+ print("UPDATING %s" % target_versionfile)
+ write_to_version_file(target_versionfile, versions)
+
+ _build_exe.run(self)
+ os.unlink(target_versionfile)
+ with open(cfg.versionfile_source, "w") as f:
+ LONG = LONG_VERSION_PY[cfg.VCS]
+ f.write(LONG %
+ {"DOLLAR": "$",
+ "STYLE": cfg.style,
+ "TAG_PREFIX": cfg.tag_prefix,
+ "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+ "VERSIONFILE_SOURCE": cfg.versionfile_source,
+ })
+ cmds["build_exe"] = cmd_build_exe
+ del cmds["build_py"]
+
+ if 'py2exe' in sys.modules: # py2exe enabled?
+ try:
+ from py2exe.distutils_buildexe import py2exe as _py2exe # py3
+ except ImportError:
+ from py2exe.build_exe import py2exe as _py2exe # py2
+
+ class cmd_py2exe(_py2exe):
+ def run(self):
+ root = get_root()
+ cfg = get_config_from_root(root)
+ versions = get_versions()
+ target_versionfile = cfg.versionfile_source
+ print("UPDATING %s" % target_versionfile)
+ write_to_version_file(target_versionfile, versions)
+
+ _py2exe.run(self)
+ os.unlink(target_versionfile)
+ with open(cfg.versionfile_source, "w") as f:
+ LONG = LONG_VERSION_PY[cfg.VCS]
+ f.write(LONG %
+ {"DOLLAR": "$",
+ "STYLE": cfg.style,
+ "TAG_PREFIX": cfg.tag_prefix,
+ "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+ "VERSIONFILE_SOURCE": cfg.versionfile_source,
+ })
+ cmds["py2exe"] = cmd_py2exe
+
+ # we override different "sdist" commands for both environments
+ if "setuptools" in sys.modules:
+ from setuptools.command.sdist import sdist as _sdist
+ else:
+ from distutils.command.sdist import sdist as _sdist
+
+ class cmd_sdist(_sdist):
+ def run(self):
+ versions = get_versions()
+ self._versioneer_generated_versions = versions
+ # unless we update this, the command will keep using the old
+ # version
+ self.distribution.metadata.version = versions["version"]
+ return _sdist.run(self)
+
+ def make_release_tree(self, base_dir, files):
+ root = get_root()
+ cfg = get_config_from_root(root)
+ _sdist.make_release_tree(self, base_dir, files)
+ # now locate _version.py in the new base_dir directory
+ # (remembering that it may be a hardlink) and replace it with an
+ # updated value
+ target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
+ print("UPDATING %s" % target_versionfile)
+ write_to_version_file(target_versionfile,
+ self._versioneer_generated_versions)
+ cmds["sdist"] = cmd_sdist
+
+ return cmds
+
+
+CONFIG_ERROR = """
+setup.cfg is missing the necessary Versioneer configuration. You need
+a section like:
+
+ [versioneer]
+ VCS = git
+ style = pep440
+ versionfile_source = src/myproject/_version.py
+ versionfile_build = myproject/_version.py
+ tag_prefix =
+ parentdir_prefix = myproject-
+
+You will also need to edit your setup.py to use the results:
+
+ import versioneer
+ setup(version=versioneer.get_version(),
+ cmdclass=versioneer.get_cmdclass(), ...)
+
+Please read the docstring in ./versioneer.py for configuration instructions,
+edit setup.cfg, and re-run the installer or 'python versioneer.py setup'.
+"""
+
+SAMPLE_CONFIG = """
+# See the docstring in versioneer.py for instructions. Note that you must
+# re-run 'versioneer.py setup' after changing this section, and commit the
+# resulting files.
+
+[versioneer]
+#VCS = git
+#style = pep440
+#versionfile_source =
+#versionfile_build =
+#tag_prefix =
+#parentdir_prefix =
+
+"""
+
+INIT_PY_SNIPPET = """
+from ._version import get_versions
+__version__ = get_versions()['version']
+del get_versions
+"""
+
+
+def do_setup():
+ """Main VCS-independent setup function for installing Versioneer."""
+ root = get_root()
+ try:
+ cfg = get_config_from_root(root)
+ except (EnvironmentError, configparser.NoSectionError,
+ configparser.NoOptionError) as e:
+ if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
+ print("Adding sample versioneer config to setup.cfg",
+ file=sys.stderr)
+ with open(os.path.join(root, "setup.cfg"), "a") as f:
+ f.write(SAMPLE_CONFIG)
+ print(CONFIG_ERROR, file=sys.stderr)
+ return 1
+
+ print(" creating %s" % cfg.versionfile_source)
+ with open(cfg.versionfile_source, "w") as f:
+ LONG = LONG_VERSION_PY[cfg.VCS]
+ f.write(LONG % {"DOLLAR": "$",
+ "STYLE": cfg.style,
+ "TAG_PREFIX": cfg.tag_prefix,
+ "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+ "VERSIONFILE_SOURCE": cfg.versionfile_source,
+ })
+
+ ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
+ "__init__.py")
+ if os.path.exists(ipy):
+ try:
+ with open(ipy, "r") as f:
+ old = f.read()
+ except EnvironmentError:
+ old = ""
+ if INIT_PY_SNIPPET not in old:
+ print(" appending to %s" % ipy)
+ with open(ipy, "a") as f:
+ f.write(INIT_PY_SNIPPET)
+ else:
+ print(" %s unmodified" % ipy)
+ else:
+ print(" %s doesn't exist, ok" % ipy)
+ ipy = None
+
+ # Make sure both the top-level "versioneer.py" and versionfile_source
+ # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
+ # they'll be copied into source distributions. Pip won't be able to
+ # install the package without this.
+ manifest_in = os.path.join(root, "MANIFEST.in")
+ simple_includes = set()
+ try:
+ with open(manifest_in, "r") as f:
+ for line in f:
+ if line.startswith("include "):
+ for include in line.split()[1:]:
+ simple_includes.add(include)
+ except EnvironmentError:
+ pass
+ # That doesn't cover everything MANIFEST.in can do
+ # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
+ # it might give some false negatives. Appending redundant 'include'
+ # lines is safe, though.
+ if "versioneer.py" not in simple_includes:
+ print(" appending 'versioneer.py' to MANIFEST.in")
+ with open(manifest_in, "a") as f:
+ f.write("include versioneer.py\n")
+ else:
+ print(" 'versioneer.py' already in MANIFEST.in")
+ if cfg.versionfile_source not in simple_includes:
+ print(" appending versionfile_source ('%s') to MANIFEST.in" %
+ cfg.versionfile_source)
+ with open(manifest_in, "a") as f:
+ f.write("include %s\n" % cfg.versionfile_source)
+ else:
+ print(" versionfile_source already in MANIFEST.in")
+
+ # Make VCS-specific changes. For git, this means creating/changing
+ # .gitattributes to mark _version.py for export-subst keyword
+ # substitution.
+ do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
+ return 0
+
+
+def scan_setup_py():
+ """Validate the contents of setup.py against Versioneer's expectations."""
+ found = set()
+ setters = False
+ errors = 0
+ with open("setup.py", "r") as f:
+ for line in f.readlines():
+ if "import versioneer" in line:
+ found.add("import")
+ if "versioneer.get_cmdclass()" in line:
+ found.add("cmdclass")
+ if "versioneer.get_version()" in line:
+ found.add("get_version")
+ if "versioneer.VCS" in line:
+ setters = True
+ if "versioneer.versionfile_source" in line:
+ setters = True
+ if len(found) != 3:
+ print("")
+ print("Your setup.py appears to be missing some important items")
+ print("(but I might be wrong). Please make sure it has something")
+ print("roughly like the following:")
+ print("")
+ print(" import versioneer")
+ print(" setup( version=versioneer.get_version(),")
+ print(" cmdclass=versioneer.get_cmdclass(), ...)")
+ print("")
+ errors += 1
+ if setters:
+ print("You should remove lines like 'versioneer.VCS = ' and")
+ print("'versioneer.versionfile_source = ' . This configuration")
+ print("now lives in setup.cfg, and should be removed from setup.py")
+ print("")
+ errors += 1
+ return errors
+
+
+if __name__ == "__main__":
+ cmd = sys.argv[1]
+ if cmd == "setup":
+ errors = do_setup()
+ errors += scan_setup_py()
+ if errors:
+ sys.exit(1)
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyorbital/commit/8c3639b132680b8b9279b3e4916e267e5c1623d8
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyorbital/commit/8c3639b132680b8b9279b3e4916e267e5c1623d8
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20181111/98a605c1/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list