forked from pool/python-pandas-datareader
- Add pandas-datareader-pr978-setup.patch because versioneer is not compatible with python 3.12 * gh#pydata/pandas-datareader#978 - Some of the unit tests fail when run with online connection locally. There have been recent commits but no release since 2021. This package might not work as expected. OBS-URL: https://build.opensuse.org/request/show/1144888 OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:numeric/python-pandas-datareader?expand=0&rev=14
3496 lines
130 KiB
Diff
3496 lines
130 KiB
Diff
From 4d697317e637b3345ca753d990eb81f7676efcda Mon Sep 17 00:00:00 2001
|
|
From: Kevin Sheppard <kevin.k.sheppard@gmail.com>
|
|
Date: Tue, 24 Oct 2023 08:51:10 +0100
|
|
Subject: [PATCH 1/8] MAINT: Modernize setup
|
|
|
|
Move away from setup.py
|
|
Bump requirements
|
|
Update CI
|
|
Rerun black, isort, flake8
|
|
---
|
|
MANIFEST.in | 2 -
|
|
azure-pipelines.yml | 2 -
|
|
ci/azure/azure_template_posix.yml | 25 +-
|
|
ci/azure/azure_template_windows.yml | 20 +-
|
|
ci/pypi-install.sh | 17 -
|
|
pandas_datareader/__init__.py | 5 +-
|
|
pandas_datareader/_utils.py | 4 +-
|
|
pandas_datareader/_version.py | 556 -----
|
|
pandas_datareader/av/forex.py | 1 -
|
|
pandas_datareader/base.py | 1 -
|
|
pandas_datareader/compat/__init__.py | 3 +-
|
|
pandas_datareader/famafrench.py | 2 -
|
|
pandas_datareader/fred.py | 4 +-
|
|
pandas_datareader/io/sdmx.py | 3 -
|
|
.../tests/av/test_av_time_series.py | 1 -
|
|
.../tests/io/data/jsdmx/__init__.py | 0
|
|
.../tests/io/data/sdmx/__init__.py | 0
|
|
pandas_datareader/tests/io/test_jsdmx.py | 2 +-
|
|
pandas_datareader/tests/test_fred.py | 1 -
|
|
pandas_datareader/tests/test_wb.py | 5 -
|
|
.../tests/yahoo/data/__init__.py | 0
|
|
pandas_datareader/tests/yahoo/test_options.py | 4 +-
|
|
pandas_datareader/wb.py | 5 +-
|
|
pandas_datareader/yahoo/daily.py | 1 -
|
|
pandas_datareader/yahoo/options.py | 2 -
|
|
pyproject.toml | 88 +
|
|
requirements-dev.txt | 6 +-
|
|
requirements.txt | 2 +-
|
|
setup.cfg | 74 -
|
|
setup.py | 18 -
|
|
tox.ini | 10 -
|
|
versioneer.py | 1886 -----------------
|
|
32 files changed, 126 insertions(+), 2624 deletions(-)
|
|
delete mode 100644 ci/pypi-install.sh
|
|
delete mode 100644 pandas_datareader/_version.py
|
|
create mode 100644 pandas_datareader/tests/io/data/jsdmx/__init__.py
|
|
create mode 100644 pandas_datareader/tests/io/data/sdmx/__init__.py
|
|
create mode 100644 pandas_datareader/tests/yahoo/data/__init__.py
|
|
create mode 100644 pyproject.toml
|
|
delete mode 100644 setup.cfg
|
|
delete mode 100644 setup.py
|
|
delete mode 100644 tox.ini
|
|
delete mode 100644 versioneer.py
|
|
|
|
Index: pandas-datareader-0.10.0/MANIFEST.in
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/MANIFEST.in
|
|
+++ pandas-datareader-0.10.0/MANIFEST.in
|
|
@@ -3,8 +3,6 @@ include LICENSE.md
|
|
include requirements.txt
|
|
include requirements-dev.txt
|
|
include pandas_datareader/*.py
|
|
-
|
|
include pandas_datareader/tests/*.py
|
|
include pandas_datareader/tests/data/*
|
|
-include versioneer.py
|
|
include pandas_datareader/_version.py
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/__init__.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/__init__.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/__init__.py
|
|
@@ -1,7 +1,7 @@
|
|
import os
|
|
import sys
|
|
|
|
-from ._version import get_versions
|
|
+from ._version import __version__
|
|
from .data import (
|
|
DataReader,
|
|
Options,
|
|
@@ -32,9 +32,6 @@ from .data import (
|
|
|
|
PKG = os.path.dirname(__file__)
|
|
|
|
-__version__ = get_versions()["version"]
|
|
-del get_versions
|
|
-
|
|
__all__ = [
|
|
"__version__",
|
|
"get_components_yahoo",
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/_utils.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/_utils.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/_utils.py
|
|
@@ -46,8 +46,8 @@ def _sanitize_dates(start, end):
|
|
try:
|
|
start = to_datetime(start)
|
|
end = to_datetime(end)
|
|
- except (TypeError, ValueError):
|
|
- raise ValueError("Invalid date format.")
|
|
+ except (TypeError, ValueError) as exc:
|
|
+ raise ValueError("Invalid date format.") from exc
|
|
if start > end:
|
|
raise ValueError("start must be an earlier date than end")
|
|
return start, end
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/av/forex.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/av/forex.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/av/forex.py
|
|
@@ -30,7 +30,6 @@ class AVForexReader(AlphaVantage):
|
|
def __init__(
|
|
self, symbols=None, retry_count=3, pause=0.1, session=None, api_key=None
|
|
):
|
|
-
|
|
super(AVForexReader, self).__init__(
|
|
symbols=symbols,
|
|
start=None,
|
|
@@ -57,7 +56,7 @@ class AVForexReader(AlphaVantage):
|
|
"Please input a currency pair "
|
|
"formatted 'FROM/TO' or a list of "
|
|
"currency symbols"
|
|
- )
|
|
+ ) from e
|
|
|
|
@property
|
|
def function(self):
|
|
@@ -89,8 +88,8 @@ class AVForexReader(AlphaVantage):
|
|
def _read_lines(self, out):
|
|
try:
|
|
df = pd.DataFrame.from_dict(out[self.data_key], orient="index")
|
|
- except KeyError:
|
|
- raise RemoteDataError()
|
|
+ except KeyError as exc:
|
|
+ raise RemoteDataError() from exc
|
|
df.sort_index(ascending=True, inplace=True)
|
|
df.index = [id[3:] for id in df.index]
|
|
return df
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/base.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/base.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/base.py
|
|
@@ -1,4 +1,5 @@
|
|
import datetime
|
|
+from io import StringIO
|
|
import time
|
|
from urllib.parse import urlencode
|
|
import warnings
|
|
@@ -13,13 +14,6 @@ from pandas_datareader._utils import (
|
|
_init_session,
|
|
_sanitize_dates,
|
|
)
|
|
-from pandas_datareader.compat import (
|
|
- PANDAS_0230,
|
|
- StringIO,
|
|
- binary_type,
|
|
- bytes_to_str,
|
|
- string_types,
|
|
-)
|
|
|
|
|
|
class _BaseReader(object):
|
|
@@ -57,7 +51,6 @@ class _BaseReader(object):
|
|
session=None,
|
|
freq=None,
|
|
):
|
|
-
|
|
self.symbols = symbols
|
|
|
|
start, end = _sanitize_dates(start or self.default_start_date, end)
|
|
@@ -125,8 +118,8 @@ class _BaseReader(object):
|
|
"{} request returned no data; check URL for invalid "
|
|
"inputs: {}".format(service, self.url)
|
|
)
|
|
- if isinstance(text, binary_type):
|
|
- out.write(bytes_to_str(text))
|
|
+ if isinstance(text, bytes):
|
|
+ out.write(text.decode("utf-8"))
|
|
else:
|
|
out.write(text)
|
|
out.seek(0)
|
|
@@ -249,7 +242,7 @@ class _DailyBaseReader(_BaseReader):
|
|
def read(self):
|
|
"""Read data"""
|
|
# If a single symbol, (e.g., 'GOOG')
|
|
- if isinstance(self.symbols, (string_types, int)):
|
|
+ if isinstance(self.symbols, (str, int)):
|
|
df = self._read_one_data(self.url, params=self._get_params(self.symbols))
|
|
# Or multiple symbols, (e.g., ['GOOG', 'AAPL', 'MSFT'])
|
|
elif isinstance(self.symbols, DataFrame):
|
|
@@ -269,7 +262,7 @@ class _DailyBaseReader(_BaseReader):
|
|
passed.append(sym)
|
|
except (IOError, KeyError):
|
|
msg = "Failed to read symbol: {0!r}, replacing with NaN."
|
|
- warnings.warn(msg.format(sym), SymbolWarning)
|
|
+ warnings.warn(msg.format(sym), SymbolWarning, stacklevel=2)
|
|
failed.append(sym)
|
|
|
|
if len(passed) == 0:
|
|
@@ -281,16 +274,13 @@ class _DailyBaseReader(_BaseReader):
|
|
df_na[:] = np.nan
|
|
for sym in failed:
|
|
stocks[sym] = df_na
|
|
- if PANDAS_0230:
|
|
result = concat(stocks, sort=True).unstack(level=0)
|
|
- else:
|
|
- result = concat(stocks).unstack(level=0)
|
|
- result.columns.names = ["Attributes", "Symbols"]
|
|
+ result.columns.names = ["Attributes", "Symbols"]
|
|
return result
|
|
- except AttributeError:
|
|
+ except AttributeError as exc:
|
|
# cannot construct a panel with just 1D nans indicating no data
|
|
msg = "No data fetched using {0!r}"
|
|
- raise RemoteDataError(msg.format(self.__class__.__name__))
|
|
+ raise RemoteDataError(msg.format(self.__class__.__name__)) from exc
|
|
|
|
|
|
def _in_chunks(seq, size):
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/compat/__init__.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/compat/__init__.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/compat/__init__.py
|
|
@@ -1,4 +1,3 @@
|
|
-from distutils.version import LooseVersion
|
|
from functools import reduce
|
|
from io import StringIO
|
|
from urllib.error import HTTPError
|
|
@@ -8,33 +7,19 @@ from pandas.api.types import is_list_lik
|
|
from pandas.io import common as com
|
|
from pandas.testing import assert_frame_equal
|
|
|
|
-PANDAS_VERSION = LooseVersion(pd.__version__)
|
|
-
|
|
-PANDAS_0210 = PANDAS_VERSION >= LooseVersion("0.21.0")
|
|
-PANDAS_0220 = PANDAS_VERSION >= LooseVersion("0.22.0")
|
|
-PANDAS_0230 = PANDAS_VERSION >= LooseVersion("0.23.0")
|
|
|
|
__all__ = [
|
|
"HTTPError",
|
|
"StringIO",
|
|
- "PANDAS_0210",
|
|
- "PANDAS_0220",
|
|
- "PANDAS_0230",
|
|
"get_filepath_or_buffer",
|
|
- "str_to_bytes",
|
|
- "string_types",
|
|
"assert_frame_equal",
|
|
"is_list_like",
|
|
"is_number",
|
|
- "lmap",
|
|
- "lrange",
|
|
- "concat",
|
|
"reduce",
|
|
]
|
|
|
|
|
|
def get_filepath_or_buffer(filepath_or_buffer, encoding=None, compression=None):
|
|
-
|
|
# Dictionaries are no longer considered valid inputs
|
|
# for "get_filepath_or_buffer" starting in pandas >= 0.20.0
|
|
if isinstance(filepath_or_buffer, dict):
|
|
@@ -49,34 +34,3 @@ def get_filepath_or_buffer(filepath_or_b
|
|
filepath_or_buffer, encoding=encoding, compression=None
|
|
)
|
|
return tmp
|
|
-
|
|
-
|
|
-string_types = (str,)
|
|
-binary_type = bytes
|
|
-
|
|
-
|
|
-def str_to_bytes(s, encoding=None):
|
|
- if isinstance(s, bytes):
|
|
- return s
|
|
- return s.encode(encoding or "ascii")
|
|
-
|
|
-
|
|
-def bytes_to_str(b, encoding=None):
|
|
- return b.decode(encoding or "utf-8")
|
|
-
|
|
-
|
|
-def lmap(*args, **kwargs):
|
|
- return list(map(*args, **kwargs))
|
|
-
|
|
-
|
|
-def lrange(*args, **kwargs):
|
|
- return list(range(*args, **kwargs))
|
|
-
|
|
-
|
|
-def concat(*args, **kwargs):
|
|
- """
|
|
- Shim to wokr around sort keyword
|
|
- """
|
|
- if not PANDAS_0230 and "sort" in kwargs:
|
|
- del kwargs["sort"]
|
|
- return pd.concat(*args, **kwargs)
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/famafrench.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/famafrench.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/famafrench.py
|
|
@@ -6,7 +6,7 @@ from zipfile import ZipFile
|
|
from pandas import read_csv, to_datetime
|
|
|
|
from pandas_datareader.base import _BaseReader
|
|
-from pandas_datareader.compat import StringIO, lmap
|
|
+from pandas_datareader.compat import StringIO
|
|
|
|
_URL = "http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/"
|
|
_URL_PREFIX = "ftp/"
|
|
@@ -75,7 +75,6 @@ class FamaFrenchReader(_BaseReader):
|
|
return super(FamaFrenchReader, self).read()
|
|
|
|
def _read_one_data(self, url, params):
|
|
-
|
|
params = {
|
|
"index_col": 0,
|
|
"parse_dates": [0],
|
|
@@ -84,13 +83,12 @@ class FamaFrenchReader(_BaseReader):
|
|
|
|
# headers in these files are not valid
|
|
if self.symbols.endswith("_Breakpoints"):
|
|
-
|
|
if self.symbols.find("-") > -1:
|
|
c = ["<=0", ">0"]
|
|
else:
|
|
c = ["Count"]
|
|
r = list(range(0, 105, 5))
|
|
- params["names"] = ["Date"] + c + list(zip(r, r[1:]))
|
|
+ params["names"] = ["Date"] + c + list(zip(r, r[1:], strict=True))
|
|
|
|
if self.symbols != "Prior_2-12_Breakpoints":
|
|
params["skiprows"] = 1
|
|
@@ -146,11 +144,11 @@ class FamaFrenchReader(_BaseReader):
|
|
"""
|
|
try:
|
|
from lxml.html import document_fromstring
|
|
- except ImportError:
|
|
+ except ImportError as exc:
|
|
raise ImportError(
|
|
"Please install lxml if you want to use the "
|
|
"get_datasets_famafrench function"
|
|
- )
|
|
+ ) from exc
|
|
|
|
response = self.session.get(_URL + "data_library.html")
|
|
root = document_fromstring(response.content)
|
|
@@ -164,4 +162,4 @@ class FamaFrenchReader(_BaseReader):
|
|
if ds.startswith(_URL_PREFIX) and ds.endswith(_URL_SUFFIX)
|
|
]
|
|
|
|
- return lmap(lambda x: x[len(_URL_PREFIX) : -len(_URL_SUFFIX)], datasets)
|
|
+ return list(map(lambda x: x[len(_URL_PREFIX) : -len(_URL_SUFFIX)], datasets))
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/fred.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/fred.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/fred.py
|
|
@@ -50,15 +50,17 @@ class FredReader(_BaseReader):
|
|
)
|
|
try:
|
|
return data.truncate(self.start, self.end)
|
|
- except KeyError: # pragma: no cover
|
|
+ except KeyError as exc: # pragma: no cover
|
|
if data.iloc[3].name[7:12] == "Error":
|
|
raise IOError(
|
|
"Failed to get the data. Check that "
|
|
"{0!r} is a valid FRED series.".format(name)
|
|
- )
|
|
+ ) from exc
|
|
raise
|
|
|
|
df = concat(
|
|
- [fetch_data(url, n) for url, n in zip(urls, names)], axis=1, join="outer"
|
|
+ [fetch_data(url, n) for url, n in zip(urls, names, strict=True)],
|
|
+ axis=1,
|
|
+ join="outer",
|
|
)
|
|
return df
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/io/sdmx.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/io/sdmx.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/io/sdmx.py
|
|
@@ -7,7 +7,7 @@ import zipfile
|
|
|
|
import pandas as pd
|
|
|
|
-from pandas_datareader.compat import HTTPError, str_to_bytes
|
|
+from pandas_datareader.compat import HTTPError
|
|
from pandas_datareader.io.util import _read_content
|
|
|
|
_STRUCTURE = "{http://www.sdmx.org/resources/sdmxml/schemas/v2_1/structure}"
|
|
@@ -53,11 +53,11 @@ def read_sdmx(path_or_buf, dtype="float6
|
|
|
|
try:
|
|
structure = _get_child(root, _MESSAGE + "Structure")
|
|
- except ValueError:
|
|
+ except ValueError as exc:
|
|
# get zipped path
|
|
result = list(root.iter(_COMMON + "Text"))[1].text
|
|
if not result.startswith("http"):
|
|
- raise ValueError(result)
|
|
+ raise ValueError(result) from exc
|
|
|
|
for _ in range(60):
|
|
# wait zipped data is prepared
|
|
@@ -72,7 +72,7 @@ def read_sdmx(path_or_buf, dtype="float6
|
|
"Unable to download zipped data within 60 secs, "
|
|
"please download it manually from: {0}"
|
|
)
|
|
- raise ValueError(msg.format(result))
|
|
+ raise ValueError(msg.format(result)) from exc
|
|
|
|
idx_name = structure.get("dimensionAtObservation")
|
|
dataset = _get_child(root, _DATASET)
|
|
@@ -97,7 +97,6 @@ def read_sdmx(path_or_buf, dtype="float6
|
|
|
|
|
|
def _construct_series(values, name, dsd=None):
|
|
-
|
|
# ts defines attributes to be handled as times
|
|
times = dsd.ts if dsd is not None else []
|
|
|
|
@@ -105,7 +104,6 @@ def _construct_series(values, name, dsd=
|
|
raise ValueError("Data contains no 'Series'")
|
|
results = []
|
|
for value in values:
|
|
-
|
|
if name in times:
|
|
tvalue = [v[0] for v in value]
|
|
try:
|
|
@@ -121,7 +119,6 @@ def _construct_series(values, name, dsd=
|
|
|
|
|
|
def _construct_index(keys, dsd=None):
|
|
-
|
|
# code defines a mapping to key's internal code to its representation
|
|
codes = dsd.codes if dsd is not None else {}
|
|
|
|
@@ -237,8 +234,10 @@ def _read_zipped_sdmx(path_or_buf):
|
|
"""Unzipp data contains SDMX-XML"""
|
|
data = _read_content(path_or_buf)
|
|
|
|
+ if not isinstance(data, bytes):
|
|
+ data = data.encode("ascii")
|
|
zp = BytesIO()
|
|
- zp.write(str_to_bytes(data))
|
|
+ zp.write(data)
|
|
f = zipfile.ZipFile(zp)
|
|
files = f.namelist()
|
|
assert len(files) == 1
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/av/test_av_time_series.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/av/test_av_time_series.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/av/test_av_time_series.py
|
|
@@ -114,7 +114,6 @@ class TestAVTimeSeries(object):
|
|
|
|
@staticmethod
|
|
def _helper_df_weekly_monthly(df, adj=False):
|
|
-
|
|
expected1 = df.loc["2015-02-27"]
|
|
assert expected1["close"] == 128.46
|
|
assert expected1["high"] == 133.60
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/io/test_jsdmx.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/io/test_jsdmx.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/io/test_jsdmx.py
|
|
@@ -7,7 +7,6 @@ import pandas as pd
|
|
from pandas import testing as tm
|
|
import pytest
|
|
|
|
-from pandas_datareader.compat import PANDAS_0210
|
|
from pandas_datareader.io import read_jsdmx
|
|
|
|
pytestmark = pytest.mark.stable
|
|
@@ -18,7 +17,6 @@ def dirpath(datapath):
|
|
return datapath("io", "data")
|
|
|
|
|
|
-@pytest.mark.skipif(not PANDAS_0210, reason="Broken on old pandas")
|
|
def test_tourism(dirpath):
|
|
# OECD -> Industry and Services -> Inbound Tourism
|
|
result = read_jsdmx(os.path.join(dirpath, "jsdmx", "tourism.json"))
|
|
@@ -74,7 +72,6 @@ def test_tourism(dirpath):
|
|
tm.assert_frame_equal(jp[visitors], expected)
|
|
|
|
|
|
-@pytest.mark.skipif(not PANDAS_0210, reason="Broken on old pandas")
|
|
def test_land_use(dirpath):
|
|
# OECD -> Environment -> Resources Land Use
|
|
result = read_jsdmx(os.path.join(dirpath, "jsdmx", "land_use.json"))
|
|
@@ -148,7 +145,6 @@ def test_land_use(dirpath):
|
|
tm.assert_frame_equal(result[exp_col], expected)
|
|
|
|
|
|
-@pytest.mark.skipif(not PANDAS_0210, reason="Broken on old pandas")
|
|
def test_quartervalue(dirpath):
|
|
# https://stats.oecd.org/sdmx-json/data/QNA/AUS+AUT+BEL+CAN+CHL.GDP+B1_
|
|
# GE.CUR+VOBARSA.Q/all?startTime=2009-Q1&endTime=2011-Q4
|
|
@@ -170,7 +166,7 @@ def test_quartervalue(dirpath):
|
|
"2011-10-01",
|
|
],
|
|
dtype="datetime64[ns]",
|
|
- name=u"Period",
|
|
+ name="Period",
|
|
freq=None,
|
|
)
|
|
tm.assert_index_equal(result.index, expected)
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_fred.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_fred.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_fred.py
|
|
@@ -13,7 +13,6 @@ pytestmark = pytest.mark.stable
|
|
|
|
class TestFred(object):
|
|
def test_fred(self):
|
|
-
|
|
# Raises an exception when DataReader can't
|
|
# get a 200 response from FRED.
|
|
|
|
@@ -55,7 +54,7 @@ class TestFred(object):
|
|
|
|
def test_invalid_series(self):
|
|
name = "NOT A REAL SERIES"
|
|
- with pytest.raises(Exception):
|
|
+ with pytest.raises(Exception): # noqa: B017
|
|
web.get_data_fred(name)
|
|
|
|
def test_fred_multi(self): # pragma: no cover
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_wb.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_wb.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_wb.py
|
|
@@ -6,8 +6,6 @@ from pandas import testing as tm
|
|
import pytest
|
|
import requests
|
|
|
|
-from pandas_datareader._testing import skip_on_exception
|
|
-from pandas_datareader._utils import RemoteDataError
|
|
from pandas_datareader.wb import (
|
|
WorldBankReader,
|
|
download,
|
|
@@ -21,7 +19,6 @@ pytestmark = pytest.mark.stable
|
|
|
|
class TestWB(object):
|
|
def test_wdi_search(self):
|
|
-
|
|
# Test that a name column exists, and that some results were returned
|
|
# ...without being too strict about what the actual contents of the
|
|
# results actually are. The fact that there are some, is good enough.
|
|
@@ -45,7 +42,6 @@ class TestWB(object):
|
|
assert result.name.str.contains("GDP").any()
|
|
|
|
def test_wdi_download(self):
|
|
-
|
|
# Test a bad indicator with double (US), triple (USA),
|
|
# standard (CA, MX), non standard (KSV),
|
|
# duplicated (US, US, USA), and unknown (BLA) country codes
|
|
@@ -64,9 +60,9 @@ class TestWB(object):
|
|
"NY.GDP.PCAP.CD": {
|
|
("Canada", "2004"): 32000.0,
|
|
("Canada", "2003"): 28000.0,
|
|
- ("Kosovo", "2004"): 2000.0,
|
|
- ("Kosovo", "2003"): 2000.0,
|
|
- ("Mexico", "2004"): 7000.0,
|
|
+ ("Kosovo", "2004"): np.nan,
|
|
+ ("Kosovo", "2003"): np.nan,
|
|
+ ("Mexico", "2004"): 8000.0,
|
|
("Mexico", "2003"): 7000.0,
|
|
("United States", "2004"): 42000.0,
|
|
("United States", "2003"): 39000.0,
|
|
@@ -101,20 +97,20 @@ class TestWB(object):
|
|
tm.assert_frame_equal(result, expected)
|
|
|
|
def test_wdi_download_str(self):
|
|
-
|
|
# These are the expected results, rounded (robust against
|
|
# data revisions in the future).
|
|
expected = {
|
|
"NY.GDP.PCAP.CD": {
|
|
("Japan", "2004"): 38000.0,
|
|
("Japan", "2003"): 35000.0,
|
|
- ("Japan", "2002"): 32000.0,
|
|
+ ("Japan", "2002"): 33000.0,
|
|
("Japan", "2001"): 34000.0,
|
|
("Japan", "2000"): 39000.0,
|
|
}
|
|
}
|
|
expected = pd.DataFrame(expected)
|
|
expected = expected.sort_index()
|
|
+ expected.index.names = ("country", "year")
|
|
|
|
cntry_codes = "JP"
|
|
inds = "NY.GDP.PCAP.CD"
|
|
@@ -152,8 +148,8 @@ class TestWB(object):
|
|
result = download(
|
|
country=cntry_codes, indicator=inds, start=2003, end=2004, errors="warn"
|
|
)
|
|
- assert isinstance(result, pd.DataFrame)
|
|
- assert len(result), 2
|
|
+ assert isinstance(result, pd.DataFrame)
|
|
+ assert len(result), 2
|
|
|
|
cntry_codes = ["USA"]
|
|
inds = ["NY.GDP.PCAP.CD", "BAD_INDICATOR"]
|
|
@@ -172,11 +168,10 @@ class TestWB(object):
|
|
result = download(
|
|
country=cntry_codes, indicator=inds, start=2003, end=2004, errors="warn"
|
|
)
|
|
- assert isinstance(result, pd.DataFrame)
|
|
- assert len(result) == 2
|
|
+ assert isinstance(result, pd.DataFrame)
|
|
+ assert len(result) == 2
|
|
|
|
def test_wdi_download_w_retired_indicator(self):
|
|
-
|
|
cntry_codes = ["CA", "MX", "US"]
|
|
# Despite showing up in the search feature, and being listed online,
|
|
# the api calls to GDPPCKD don't work in their own query builder, nor
|
|
@@ -192,7 +187,7 @@ class TestWB(object):
|
|
inds = ["GDPPCKD"]
|
|
|
|
with pytest.raises(ValueError):
|
|
- result = download(
|
|
+ download(
|
|
country=cntry_codes,
|
|
indicator=inds,
|
|
start=2003,
|
|
@@ -200,21 +195,12 @@ class TestWB(object):
|
|
errors="ignore",
|
|
)
|
|
|
|
- # If it ever gets here, it means WB unretired the indicator.
|
|
- # even if they dropped it completely, it would still
|
|
- # get caught above
|
|
- # or the WB API changed somehow in a really
|
|
- # unexpected way.
|
|
- if len(result) > 0: # pragma: no cover
|
|
- pytest.skip("Invalid results")
|
|
-
|
|
def test_wdi_download_w_crash_inducing_countrycode(self):
|
|
-
|
|
cntry_codes = ["CA", "MX", "US", "XXX"]
|
|
inds = ["NY.GDP.PCAP.CD"]
|
|
|
|
with pytest.raises(ValueError):
|
|
- result = download(
|
|
+ download(
|
|
country=cntry_codes,
|
|
indicator=inds,
|
|
start=2003,
|
|
@@ -222,13 +208,6 @@ class TestWB(object):
|
|
errors="ignore",
|
|
)
|
|
|
|
- # If it ever gets here, it means the country code XXX
|
|
- # got used by WB
|
|
- # or the WB API changed somehow in a really
|
|
- # unexpected way.
|
|
- if len(result) > 0: # pragma: no cover
|
|
- pytest.skip("Invalid results")
|
|
-
|
|
def test_wdi_get_countries(self):
|
|
result1 = get_countries()
|
|
result2 = WorldBankReader().get_countries()
|
|
@@ -267,7 +246,7 @@ class TestWB(object):
|
|
assert sorted(result.columns) == sorted(exp_col)
|
|
assert len(result) > 10000
|
|
|
|
- @skip_on_exception(RemoteDataError)
|
|
+ @pytest.mark.xfail(reason="World Bank API changed, no data returned")
|
|
def test_wdi_download_monthly(self):
|
|
expected = {
|
|
"COPPER": {
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/yahoo/test_options.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/yahoo/test_options.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/yahoo/test_options.py
|
|
@@ -18,7 +18,6 @@ def aapl():
|
|
|
|
@pytest.fixture
|
|
def month():
|
|
-
|
|
# AAPL has monthlies
|
|
today = datetime.today()
|
|
month = today.month + 1
|
|
@@ -31,7 +30,6 @@ def month():
|
|
|
|
@pytest.fixture
|
|
def year():
|
|
-
|
|
# AAPL has monthlies
|
|
today = datetime.today()
|
|
year = today.year
|
|
@@ -100,7 +98,7 @@ class TestYahooOptions(object):
|
|
]
|
|
)
|
|
tm.assert_index_equal(df.columns, exp_columns)
|
|
- assert df.index.names == [u"Strike", u"Expiry", u"Type", u"Symbol"]
|
|
+ assert df.index.names == ["Strike", "Expiry", "Type", "Symbol"]
|
|
|
|
dtypes = [
|
|
np.dtype(x)
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/wb.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/wb.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/wb.py
|
|
@@ -1,12 +1,12 @@
|
|
# -*- coding: utf-8 -*-
|
|
|
|
+from functools import reduce
|
|
import warnings
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
from pandas_datareader.base import _BaseReader
|
|
-from pandas_datareader.compat import lrange, reduce, string_types
|
|
|
|
# This list of country codes was pulled from wikipedia during October 2014.
|
|
# While some exceptions do exist, it is the best proxy for countries supported
|
|
@@ -562,10 +562,9 @@ class WorldBankReader(_BaseReader):
|
|
session=None,
|
|
errors="warn",
|
|
):
|
|
-
|
|
if symbols is None:
|
|
symbols = ["NY.GDP.MKTP.CD", "NY.GNS.ICTR.ZS"]
|
|
- elif isinstance(symbols, string_types):
|
|
+ elif isinstance(symbols, str):
|
|
symbols = [symbols]
|
|
|
|
super(WorldBankReader, self).__init__(
|
|
@@ -579,7 +578,7 @@ class WorldBankReader(_BaseReader):
|
|
|
|
if countries is None:
|
|
countries = ["MX", "CA", "US"]
|
|
- elif isinstance(countries, string_types):
|
|
+ elif isinstance(countries, str):
|
|
countries = [countries]
|
|
|
|
bad_countries = np.setdiff1d(countries, country_codes)
|
|
@@ -590,7 +589,9 @@ class WorldBankReader(_BaseReader):
|
|
raise ValueError("Invalid Country Code(s): %s" % tmp)
|
|
if errors == "warn":
|
|
warnings.warn(
|
|
- "Non-standard ISO " "country codes: %s" % tmp, UserWarning
|
|
+ "Non-standard ISO country codes: %s" % tmp,
|
|
+ UserWarning,
|
|
+ stacklevel=2,
|
|
)
|
|
|
|
freq_symbols = ["M", "Q", "A", None]
|
|
@@ -654,9 +655,9 @@ class WorldBankReader(_BaseReader):
|
|
except ValueError as e:
|
|
msg = str(e) + " Indicator: " + indicator
|
|
if self.errors == "raise":
|
|
- raise ValueError(msg)
|
|
+ raise ValueError(msg) from e
|
|
elif self.errors == "warn":
|
|
- warnings.warn(msg)
|
|
+ warnings.warn(msg, stacklevel=2)
|
|
|
|
# Confirm we actually got some data, and build Dataframe
|
|
if len(data) > 0:
|
|
@@ -769,7 +770,7 @@ class WorldBankReader(_BaseReader):
|
|
|
|
# Clean output
|
|
data = data.sort_values(by="id")
|
|
- data.index = pd.Index(lrange(data.shape[0]))
|
|
+ data.index = pd.Index(list(range((data.shape[0]))))
|
|
|
|
# cache
|
|
_cached_series = data.copy()
|
|
@@ -820,7 +821,7 @@ def download(
|
|
end=2005,
|
|
freq=None,
|
|
errors="warn",
|
|
- **kwargs
|
|
+ **kwargs,
|
|
):
|
|
"""
|
|
Download data series from the World Bank's World Development Indicators
|
|
@@ -865,7 +866,7 @@ def download(
|
|
end=end,
|
|
freq=freq,
|
|
errors=errors,
|
|
- **kwargs
|
|
+ **kwargs,
|
|
).read()
|
|
|
|
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/yahoo/daily.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/yahoo/daily.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/yahoo/daily.py
|
|
@@ -151,9 +151,9 @@ class YahooDailyReader(_DailyBaseReader)
|
|
try:
|
|
j = json.loads(re.search(ptrn, resp.text, re.DOTALL).group(1))
|
|
data = j["context"]["dispatcher"]["stores"]["HistoricalPriceStore"]
|
|
- except KeyError:
|
|
+ except KeyError as exc:
|
|
msg = "No data fetched for symbol {} using {}"
|
|
- raise RemoteDataError(msg.format(symbol, self.__class__.__name__))
|
|
+ raise RemoteDataError(msg.format(symbol, self.__class__.__name__)) from exc
|
|
|
|
# price data
|
|
prices = DataFrame(data["prices"])
|
|
@@ -175,7 +175,6 @@ class YahooDailyReader(_DailyBaseReader)
|
|
|
|
# dividends & splits data
|
|
if self.get_actions and data["eventsData"]:
|
|
-
|
|
actions = DataFrame(data["eventsData"])
|
|
actions.columns = [col.capitalize() for col in actions.columns]
|
|
actions["Date"] = to_datetime(
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/yahoo/options.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/yahoo/options.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/yahoo/options.py
|
|
@@ -137,14 +137,15 @@ class Options(_OptionBaseReader):
|
|
).sort_index()
|
|
|
|
def _option_from_url(self, url):
|
|
-
|
|
jd = self._parse_url(url)
|
|
result = jd["optionChain"]["result"]
|
|
try:
|
|
calls = result["options"]["calls"]
|
|
puts = result["options"]["puts"]
|
|
- except IndexError:
|
|
- raise RemoteDataError("Option json not available " "for url: %s" % url)
|
|
+ except IndexError as exc:
|
|
+ raise RemoteDataError(
|
|
+ "Option json not available " "for url: %s" % url
|
|
+ ) from exc
|
|
|
|
self.underlying_price = (
|
|
result["quote"]["regularMarketPrice"]
|
|
@@ -562,7 +563,7 @@ class Options(_OptionBaseReader):
|
|
on Yahoo and may change.
|
|
|
|
"""
|
|
- warnings.warn("get_forward_data() is deprecated", FutureWarning)
|
|
+ warnings.warn("get_forward_data() is deprecated", FutureWarning, stacklevel=2)
|
|
end_date = dt.date.today() + MonthEnd(months)
|
|
dates = [date for date in self.expiry_dates if date <= end_date.date()]
|
|
data = self._get_data_in_date_range(dates, call=call, put=put)
|
|
@@ -620,7 +621,6 @@ class Options(_OptionBaseReader):
|
|
return self._load_data()
|
|
|
|
def _get_data_in_date_range(self, dates, call=True, put=True):
|
|
-
|
|
to_ret = Series({"call": call, "put": put})
|
|
to_ret = to_ret[to_ret].index
|
|
|
|
Index: pandas-datareader-0.10.0/pyproject.toml
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ pandas-datareader-0.10.0/pyproject.toml
|
|
@@ -0,0 +1,96 @@
|
|
+[build-system]
|
|
+requires = ["setuptools>=64", "setuptools_scm>=8,<9", "wheel"]
|
|
+build-backend = "setuptools.build_meta"
|
|
+
|
|
+[project]
|
|
+name = "pandas-datareader"
|
|
+dynamic = ["version", "dependencies", "readme"]
|
|
+requires-python = ">=3.8"
|
|
+license = {file = "LICENSE.md"}
|
|
+description = "Pandas-compatible data readers. Formerly a component of pandas."
|
|
+authors = [
|
|
+ {name = "The PyData Development Team", email = "pydata@googlegroups.com"},
|
|
+]
|
|
+classifiers =[
|
|
+ "Development Status :: 5 - Production/Stable",
|
|
+ "Environment :: Console",
|
|
+ "Intended Audience :: Science/Research",
|
|
+ "License :: OSI Approved :: BSD License",
|
|
+ "Operating System :: OS Independent",
|
|
+ "Programming Language :: Python",
|
|
+ "Programming Language :: Python :: 3",
|
|
+ "Programming Language :: Python :: 3 :: Only",
|
|
+ "Programming Language :: Python :: 3.8",
|
|
+ "Programming Language :: Python :: 3.9",
|
|
+ "Programming Language :: Python :: 3.10",
|
|
+ "Programming Language :: Python :: 3.11",
|
|
+ "Programming Language :: Python :: 3.12",
|
|
+ "Topic :: Scientific/Engineering"
|
|
+]
|
|
+
|
|
+[project.urls]
|
|
+Homepage = "https://pandas.pydata.org"
|
|
+Documentation = "https://pandas-datareader.readthedocs.io/en/latest/"
|
|
+Repository = "https://github.com/pydata/pandas-datareader.git"
|
|
+"Bug Tracker" = "https://github.com/pydata/pandas-datareader/issues"
|
|
+
|
|
+
|
|
+[tool.setuptools]
|
|
+package-dir = {"" = "."}
|
|
+
|
|
+[tool.setuptools.dynamic]
|
|
+dependencies = {file = ["requirements.txt"]}
|
|
+readme = {file = ["README.md"], content-type = "text/markdown"}
|
|
+
|
|
+[tool.setuptools_scm]
|
|
+version_file = "pandas_datareader/_version.py"
|
|
+
|
|
+[tool.isort]
|
|
+profile="black"
|
|
+known_compat="pandas_datareader.compat.*"
|
|
+sections=["FUTURE","COMPAT","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
|
|
+known_first_party="pandas_datareader"
|
|
+known_third_party=["numpy","pandas","pytest","requests"]
|
|
+combine_as_imports = true
|
|
+force_sort_within_sections = true
|
|
+
|
|
+[tool.pytest.ini_options]
|
|
+minversion = "6.0"
|
|
+addopts = "-ra -q"
|
|
+testpaths = ["pandas_datareader"]
|
|
+markers = [
|
|
+ "stable: mark a test as applying to a stable reader",
|
|
+ "requires_api_key: mark a test as requiring an API key",
|
|
+ "alpha_vantage: mark a test of the AlphaVantage reader",
|
|
+ "quandl: mark a test of the Quandl reader"
|
|
+ ]
|
|
+filterwarnings =[
|
|
+ "ignore:`np.bool` is a deprecated alias:DeprecationWarning:pandas.core.indexes",
|
|
+ "ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.core.indexes",
|
|
+ "ignore:`np.float` is a deprecated alias:DeprecationWarning:pandas.core.indexes",
|
|
+ "ignore:`np.complex` is a deprecated alias:DeprecationWarning:pandas.core.indexes",
|
|
+ "ignore:`np.bool` is a deprecated alias:DeprecationWarning:pandas.core.internals.blocks",
|
|
+ "ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.core.internals.blocks",
|
|
+ "ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.core.internals.construction",
|
|
+ "ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.io.parsers",
|
|
+ "ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.core.dtypes.cast",
|
|
+ "ignore:`np.float` is a deprecated alias:DeprecationWarning:pandas.core.internals.blocks",
|
|
+ "ignore:`np.complex` is a deprecated alias:DeprecationWarning:pandas.core.internals.blocks",
|
|
+ "ignore:Converting `np.inexact` or `np.floating` to a dtype:DeprecationWarning:pandas.core.indexes",
|
|
+]
|
|
+
|
|
+
|
|
+[tool.flake8]
|
|
+ignore = ["E203", "E266", "E501", "W503"]
|
|
+max-line-length = 88
|
|
+max-complexity = 18
|
|
+select = ["B","C","E","F","W","T4","B9"]
|
|
+
|
|
+[tool.black]
|
|
+target-version = ["py38", "py39", "py310","py311","py312"]
|
|
+required-version = "23.10.1"
|
|
+exclude = """
|
|
+(
|
|
+ pandas_datareader/_version.py
|
|
+)
|
|
+"""
|
|
Index: pandas-datareader-0.10.0/requirements-dev.txt
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/requirements-dev.txt
|
|
+++ pandas-datareader-0.10.0/requirements-dev.txt
|
|
@@ -1,5 +1,6 @@
|
|
-black==21.5b1; python_version > '3.5'
|
|
-flake8-bugbear; python_version > '3.5'
|
|
+black==23.10.1
|
|
+isort>=5.12.0
|
|
+flake8-bugbear
|
|
coverage
|
|
codecov
|
|
coveralls
|
|
@@ -7,3 +8,4 @@ flake8
|
|
pytest
|
|
pytest-cov
|
|
wrapt
|
|
+flake8-pyproject
|
|
\ No newline at end of file
|
|
Index: pandas-datareader-0.10.0/requirements.txt
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/requirements.txt
|
|
+++ pandas-datareader-0.10.0/requirements.txt
|
|
@@ -1,3 +1,3 @@
|
|
lxml
|
|
-pandas>=0.23
|
|
+pandas>=1.5.3
|
|
requests>=2.19.0
|
|
Index: pandas-datareader-0.10.0/versioneer.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/versioneer.py
|
|
+++ /dev/null
|
|
@@ -1,1886 +0,0 @@
|
|
-# 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
|
|
-
|
|
-import errno
|
|
-import json
|
|
-import os
|
|
-import re
|
|
-import subprocess
|
|
-import sys
|
|
-
|
|
-try:
|
|
- import configparser
|
|
-except ImportError:
|
|
- import ConfigParser as configparser
|
|
-
|
|
-
|
|
-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"
|
|
-] = r'''
|
|
-# 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")
|
|
-
|
|
-
|
|
-@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
|
|
-
|
|
-
|
|
-@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}
|
|
-
|
|
-
|
|
-@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}
|
|
-'''
|
|
-
|
|
-
|
|
-@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
|
|
-
|
|
-
|
|
-@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,
|
|
- }
|
|
-
|
|
-
|
|
-@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)
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/av/__init__.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/av/__init__.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/av/__init__.py
|
|
@@ -70,18 +70,18 @@ class AlphaVantage(_BaseReader):
|
|
def _read_lines(self, out):
|
|
try:
|
|
df = pd.DataFrame.from_dict(out[self.data_key], orient="index")
|
|
- except KeyError:
|
|
+ except KeyError as exc:
|
|
if "Error Message" in out:
|
|
raise ValueError(
|
|
"The requested symbol {} could not be "
|
|
"retrieved. Check valid ticker"
|
|
".".format(self.symbols)
|
|
- )
|
|
+ ) from exc
|
|
else:
|
|
raise RemoteDataError(
|
|
" Their was an issue from the data vendor "
|
|
"side, here is their response: {}".format(out)
|
|
- )
|
|
+ ) from exc
|
|
df = df[sorted(df.columns)]
|
|
df.columns = [id[3:] for id in df.columns]
|
|
return df
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/iex/__init__.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/iex/__init__.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/iex/__init__.py
|
|
@@ -71,8 +71,8 @@ class IEX(_BaseReader):
|
|
"""
|
|
try:
|
|
content = json.loads(out.text)
|
|
- except Exception:
|
|
- raise TypeError("Failed to interpret response as JSON.")
|
|
+ except Exception as exc:
|
|
+ raise TypeError("Failed to interpret response as JSON.") from exc
|
|
|
|
for key, string in content.items():
|
|
e = "IEX Output error encountered: {}".format(string)
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/iex/stats.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/iex/stats.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/iex/stats.py
|
|
@@ -23,8 +23,9 @@ class DailySummaryReader(IEX):
|
|
import warnings
|
|
|
|
warnings.warn(
|
|
- "Daily statistics is not working due to issues with the " "IEX API",
|
|
+ "Daily statistics is not working due to issues with the IEX API",
|
|
UnstableAPIWarning,
|
|
+ stacklevel=2,
|
|
)
|
|
self.curr_date = start
|
|
super(DailySummaryReader, self).__init__(
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/io/jsdmx.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/io/jsdmx.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/io/jsdmx.py
|
|
@@ -31,9 +31,9 @@ def read_jsdmx(path_or_buf):
|
|
|
|
try:
|
|
import simplejson as json
|
|
- except ImportError:
|
|
+ except ImportError as exc:
|
|
if sys.version_info[:2] < (2, 7):
|
|
- raise ImportError("simplejson is required in python 2.6")
|
|
+ raise ImportError("simplejson is required in python 2.6") from exc
|
|
import json
|
|
|
|
if isinstance(jdata, dict):
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/nasdaq_trader.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/nasdaq_trader.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/nasdaq_trader.py
|
|
@@ -41,7 +41,9 @@ def _download_nasdaq_symbols(timeout):
|
|
ftp_session = FTP(_NASDAQ_FTP_SERVER, timeout=timeout)
|
|
ftp_session.login()
|
|
except all_errors as err:
|
|
- raise RemoteDataError("Error connecting to %r: %s" % (_NASDAQ_FTP_SERVER, err))
|
|
+ raise RemoteDataError(
|
|
+ "Error connecting to %r: %s" % (_NASDAQ_FTP_SERVER, err)
|
|
+ ) from err
|
|
|
|
lines = []
|
|
try:
|
|
@@ -49,7 +51,7 @@ def _download_nasdaq_symbols(timeout):
|
|
except all_errors as err:
|
|
raise RemoteDataError(
|
|
"Error downloading from %r: %s" % (_NASDAQ_FTP_SERVER, err)
|
|
- )
|
|
+ ) from err
|
|
finally:
|
|
ftp_session.close()
|
|
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_bankofcanada.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_bankofcanada.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_bankofcanada.py
|
|
@@ -42,7 +42,9 @@ class TestBankOfCanada(object):
|
|
date.today(),
|
|
)
|
|
|
|
- pairs = zip((1 / df)[symbol].tolist(), df_i[symbol_inverted].tolist())
|
|
+ pairs = zip(
|
|
+ (1 / df)[symbol].tolist(), df_i[symbol_inverted].tolist(), strict=True
|
|
+ )
|
|
assert all(a - b < 0.01 for a, b in pairs)
|
|
|
|
def test_bankofcanada_usd_count(self):
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_base.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_base.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_base.py
|
|
@@ -23,9 +23,9 @@ class TestBaseReader(object):
|
|
base._BaseReader([]).url
|
|
|
|
def test_invalid_format(self):
|
|
+ b = base._BaseReader([])
|
|
+ b._format = "IM_NOT_AN_IMPLEMENTED_TYPE"
|
|
with pytest.raises(NotImplementedError):
|
|
- b = base._BaseReader([])
|
|
- b._format = "IM_NOT_AN_IMPLEMENTED_TYPE"
|
|
b._read_one_data("a", None)
|
|
|
|
def test_default_start_date(self):
|
|
@@ -35,6 +35,6 @@ class TestBaseReader(object):
|
|
|
|
class TestDailyBaseReader(object):
|
|
def test_get_params(self):
|
|
+ b = base._DailyBaseReader()
|
|
with pytest.raises(NotImplementedError):
|
|
- b = base._DailyBaseReader()
|
|
b._get_params()
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_iex.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_iex.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_iex.py
|
|
@@ -31,15 +31,13 @@ class TestIEX(object):
|
|
df = get_last_iex("INVALID TICKER")
|
|
assert df.shape[0] == 0
|
|
|
|
- @pytest.mark.xfail(
|
|
- reason="IEX daily history API is returning 500 as of " "Jan 2018"
|
|
- )
|
|
+ @pytest.mark.xfail(reason="IEX daily history API is returning 500 as of Jan 2018")
|
|
def test_daily(self):
|
|
- with pytest.warns(UnstableAPIWarning):
|
|
+ with pytest.warns(UnstableAPIWarning, match="Daily statistics"):
|
|
df = get_dailysummary_iex(
|
|
start=datetime(2017, 5, 5), end=datetime(2017, 5, 6)
|
|
)
|
|
- assert df["routedVolume"].iloc[0] == 39974788
|
|
+ assert df["routedVolume"].iloc[0] == 39974788
|
|
|
|
def test_symbols(self):
|
|
df = get_iex_symbols()
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_iex_daily.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_iex_daily.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_iex_daily.py
|
|
@@ -25,17 +25,17 @@ class TestIEXDaily(object):
|
|
return datetime(2017, 5, 24)
|
|
|
|
def test_iex_bad_symbol(self):
|
|
- with pytest.raises(Exception):
|
|
+ with pytest.raises(Exception): # noqa: B017
|
|
web.DataReader("BADTICKER", "iex,", self.start, self.end)
|
|
|
|
def test_iex_bad_symbol_list(self):
|
|
- with pytest.raises(Exception):
|
|
+ with pytest.raises(Exception): # noqa: B017
|
|
web.DataReader(["AAPL", "BADTICKER"], "iex", self.start, self.end)
|
|
|
|
def test_daily_invalid_date(self):
|
|
start = datetime(2000, 1, 5)
|
|
end = datetime(2017, 5, 24)
|
|
- with pytest.raises(Exception):
|
|
+ with pytest.raises(Exception): # noqa: B017
|
|
web.DataReader(["AAPL", "TSLA"], "iex", start, end)
|
|
|
|
def test_single_symbol(self):
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/yahoo/test_yahoo.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/yahoo/test_yahoo.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/yahoo/test_yahoo.py
|
|
@@ -34,7 +34,7 @@ class TestYahoo(object):
|
|
start = datetime(2010, 1, 1)
|
|
end = datetime(2013, 1, 27)
|
|
|
|
- with pytest.raises(Exception):
|
|
+ with pytest.raises(Exception): # noqa: B017
|
|
web.DataReader("NON EXISTENT TICKER", "yahoo", start, end)
|
|
|
|
def test_get_quote_series(self):
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/yahoo/fx.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/yahoo/fx.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/yahoo/fx.py
|
|
@@ -5,7 +5,6 @@ import warnings
|
|
from pandas import DataFrame, Series, concat, to_datetime
|
|
|
|
from pandas_datareader._utils import RemoteDataError, SymbolWarning
|
|
-from pandas_datareader.compat import string_types
|
|
from pandas_datareader.yahoo.daily import YahooDailyReader
|
|
|
|
|
|
@@ -58,7 +57,7 @@ class YahooFXReader(YahooDailyReader):
|
|
"""Read data"""
|
|
try:
|
|
# If a single symbol, (e.g., 'GOOG')
|
|
- if isinstance(self.symbols, (string_types, int)):
|
|
+ if isinstance(self.symbols, (str, int)):
|
|
df = self._read_one_data(self.symbols)
|
|
|
|
# Or multiple symbols, (e.g., ['GOOG', 'AAPL', 'MSFT'])
|
|
@@ -103,7 +102,7 @@ class YahooFXReader(YahooDailyReader):
|
|
passed.append(sym)
|
|
except IOError:
|
|
msg = "Failed to read symbol: {0!r}, replacing with NaN."
|
|
- warnings.warn(msg.format(sym), SymbolWarning)
|
|
+ warnings.warn(msg.format(sym), SymbolWarning, stacklevel=2)
|
|
failed.append(sym)
|
|
|
|
if len(passed) == 0:
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/bankofcanada.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/bankofcanada.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/bankofcanada.py
|
|
@@ -1,7 +1,6 @@
|
|
from __future__ import unicode_literals
|
|
|
|
from pandas_datareader.base import _BaseReader
|
|
-from pandas_datareader.compat import string_types
|
|
|
|
|
|
class BankOfCanadaReader(_BaseReader):
|
|
@@ -16,7 +15,7 @@ class BankOfCanadaReader(_BaseReader):
|
|
@property
|
|
def url(self):
|
|
"""API URL"""
|
|
- if not isinstance(self.symbols, string_types):
|
|
+ if not isinstance(self.symbols, str):
|
|
raise ValueError("data name must be string")
|
|
|
|
return "{0}/{1}/csv".format(self._URL, self.symbols)
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/enigma.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/enigma.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/enigma.py
|
|
@@ -3,7 +3,7 @@ import time
|
|
|
|
import pandas as pd
|
|
|
|
-from pandas_datareader.base import _BaseReader, string_types
|
|
+from pandas_datareader.base import _BaseReader
|
|
from pandas_datareader.compat import StringIO
|
|
from pandas_datareader.exceptions import DEP_ERROR_MSG, ImmediateDeprecationError
|
|
|
|
@@ -71,7 +71,7 @@ class EnigmaReader(_BaseReader):
|
|
self._api_key = api_key
|
|
|
|
self._dataset_id = dataset_id
|
|
- if not isinstance(self._dataset_id, string_types):
|
|
+ if not isinstance(self._dataset_id, str):
|
|
raise ValueError(
|
|
"The Enigma dataset_id must be a string (ex: "
|
|
"'bedaf052-5fcd-4758-8d27-048ce8746c6a')"
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/eurostat.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/eurostat.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/eurostat.py
|
|
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|
import pandas as pd
|
|
|
|
from pandas_datareader.base import _BaseReader
|
|
-from pandas_datareader.compat import string_types
|
|
from pandas_datareader.io.sdmx import _read_sdmx_dsd, read_sdmx
|
|
|
|
|
|
@@ -15,7 +14,7 @@ class EurostatReader(_BaseReader):
|
|
@property
|
|
def url(self):
|
|
"""API URL"""
|
|
- if not isinstance(self.symbols, string_types):
|
|
+ if not isinstance(self.symbols, str):
|
|
raise ValueError("data name must be string")
|
|
|
|
q = "{0}/data/{1}/?startperiod={2}&endperiod={3}"
|
|
@@ -24,7 +23,7 @@ class EurostatReader(_BaseReader):
|
|
@property
|
|
def dsd_url(self):
|
|
"""API DSD URL"""
|
|
- if not isinstance(self.symbols, string_types):
|
|
+ if not isinstance(self.symbols, str):
|
|
raise ValueError("data name must be string")
|
|
|
|
return "{0}/datastructure/ESTAT/DSD_{1}".format(self._URL, self.symbols)
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/io/util.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/io/util.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/io/util.py
|
|
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|
|
|
import os
|
|
|
|
-from pandas_datareader.compat import get_filepath_or_buffer, string_types
|
|
+from pandas_datareader.compat import get_filepath_or_buffer
|
|
|
|
|
|
def _read_content(path_or_buf):
|
|
@@ -12,7 +12,7 @@ def _read_content(path_or_buf):
|
|
|
|
filepath_or_buffer = get_filepath_or_buffer(path_or_buf)[0]
|
|
|
|
- if isinstance(filepath_or_buffer, string_types):
|
|
+ if isinstance(filepath_or_buffer, str):
|
|
try:
|
|
exists = os.path.exists(filepath_or_buffer)
|
|
except (TypeError, ValueError):
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/moex.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/moex.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/moex.py
|
|
@@ -1,9 +1,10 @@
|
|
import datetime as dt
|
|
+from io import StringIO
|
|
|
|
import pandas as pd
|
|
|
|
from pandas_datareader.base import _DailyBaseReader
|
|
-from pandas_datareader.compat import StringIO, binary_type, concat, is_list_like
|
|
+from pandas_datareader.compat import is_list_like
|
|
|
|
|
|
class MoexReader(_DailyBaseReader):
|
|
@@ -108,7 +109,7 @@ class MoexReader(_DailyBaseReader):
|
|
"{} request returned no data; check URL for invalid "
|
|
"inputs: {}".format(service, self.__url_metadata)
|
|
)
|
|
- if isinstance(text, binary_type):
|
|
+ if isinstance(text, bytes):
|
|
text = text.decode("windows-1251")
|
|
|
|
header_str = "secid;boardid;"
|
|
@@ -200,7 +201,7 @@ class MoexReader(_DailyBaseReader):
|
|
"check URL or correct a date".format(self.__class__.__name__)
|
|
)
|
|
elif len(dfs) > 1:
|
|
- b = concat(dfs, axis=0, join="outer", sort=True)
|
|
+ b = pd.concat(dfs, axis=0, join="outer", sort=True)
|
|
else:
|
|
b = dfs[0]
|
|
return b
|
|
@@ -227,7 +228,7 @@ class MoexReader(_DailyBaseReader):
|
|
"{} request returned no data; check URL for invalid "
|
|
"inputs: {}".format(service, self.url)
|
|
)
|
|
- if isinstance(text, binary_type):
|
|
+ if isinstance(text, bytes):
|
|
text = text.decode("windows-1251")
|
|
return text
|
|
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/naver.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/naver.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/naver.py
|
|
@@ -3,7 +3,6 @@ from xml.etree import ElementTree
|
|
|
|
import numpy as np
|
|
from pandas import DataFrame, to_datetime
|
|
-from six import string_types
|
|
|
|
from pandas_datareader.base import _DailyBaseReader
|
|
|
|
@@ -32,7 +31,7 @@ class NaverDailyReader(_DailyBaseReader)
|
|
get_actions=False,
|
|
adjust_dividends=True,
|
|
):
|
|
- if not isinstance(symbols, string_types):
|
|
+ if not isinstance(symbols, str):
|
|
raise NotImplementedError("Bulk-fetching is not implemented")
|
|
|
|
super().__init__(
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/oecd.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/oecd.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/oecd.py
|
|
@@ -1,7 +1,6 @@
|
|
import pandas as pd
|
|
|
|
from pandas_datareader.base import _BaseReader
|
|
-from pandas_datareader.compat import string_types
|
|
from pandas_datareader.io import read_jsdmx
|
|
|
|
|
|
@@ -15,7 +14,7 @@ class OECDReader(_BaseReader):
|
|
"""API URL"""
|
|
url = "http://stats.oecd.org/SDMX-JSON/data"
|
|
|
|
- if not isinstance(self.symbols, string_types):
|
|
+ if not isinstance(self.symbols, str):
|
|
raise ValueError("data name must be string")
|
|
|
|
# API: https://data.oecd.org/api/sdmx-json-documentation/
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_stooq.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_stooq.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_stooq.py
|
|
@@ -26,6 +26,7 @@ def test_stooq_sp500():
|
|
assert f.shape[0] > 0
|
|
|
|
|
|
+@pytest.mark.xfail(reason="No longer works as of Otober 2023")
|
|
def test_stooq_clx19f():
|
|
f = get_data_stooq("CLX26.F", start="20200101", end="20200115")
|
|
assert f.shape[0] > 0
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_tsp.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_tsp.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_tsp.py
|
|
@@ -4,7 +4,7 @@ import pytest
|
|
|
|
from pandas_datareader import tsp as tsp
|
|
|
|
-pytestmark = pytest.mark.stable
|
|
+pytestmark = pytest.mark.skip(reason="TSP API has changed")
|
|
|
|
|
|
class TestTSPFunds(object):
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/yahoo/actions.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/yahoo/actions.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/yahoo/actions.py
|
|
@@ -1,6 +1,6 @@
|
|
+import pandas as pd
|
|
from pandas import DataFrame, MultiIndex
|
|
|
|
-from pandas_datareader.compat import concat
|
|
from pandas_datareader.yahoo.daily import YahooDailyReader
|
|
|
|
|
|
@@ -35,7 +35,7 @@ def _get_one_action(data):
|
|
dividends = DataFrame(data["Dividends"]).dropna()
|
|
dividends["action"] = "DIVIDEND"
|
|
dividends = dividends.rename(columns={"Dividends": "value"})
|
|
- actions = concat([actions, dividends], sort=True)
|
|
+ actions = pd.concat([actions, dividends], sort=True, axis=1)
|
|
actions = actions.sort_index(ascending=False)
|
|
|
|
if "Splits" in data.columns:
|
|
@@ -43,7 +43,7 @@ def _get_one_action(data):
|
|
splits = DataFrame(data["Splits"]).dropna()
|
|
splits["action"] = "SPLIT"
|
|
splits = splits.rename(columns={"Splits": "value"})
|
|
- actions = concat([actions, splits], sort=True)
|
|
+ actions = pd.concat([actions, splits], sort=True, axis=1)
|
|
actions = actions.sort_index(ascending=False)
|
|
|
|
return actions
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/yahoo/quotes.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/yahoo/quotes.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/yahoo/quotes.py
|
|
@@ -4,7 +4,6 @@ import json
|
|
from pandas import DataFrame
|
|
|
|
from pandas_datareader.base import _BaseReader
|
|
-from pandas_datareader.compat import string_types
|
|
from pandas_datareader.yahoo.headers import DEFAULT_HEADERS
|
|
|
|
_DEFAULT_PARAMS = {
|
|
@@ -45,7 +44,7 @@ class YahooQuotesReader(_BaseReader):
|
|
return "https://query1.finance.yahoo.com/v7/finance/quote"
|
|
|
|
def read(self):
|
|
- if isinstance(self.symbols, string_types):
|
|
+ if isinstance(self.symbols, str):
|
|
return self._read_one_data(self.url, self.params(self.symbols))
|
|
else:
|
|
data = OrderedDict()
|
|
Index: pandas-datareader-0.10.0/pandas_datareader/tests/test_econdb.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/pandas_datareader/tests/test_econdb.py
|
|
+++ pandas-datareader-0.10.0/pandas_datareader/tests/test_econdb.py
|
|
@@ -11,14 +11,18 @@ pytestmark = pytest.mark.stable
|
|
class TestEcondb(object):
|
|
def test_infer_start_end_from_symbols(self):
|
|
df = web.DataReader(
|
|
- (
|
|
- "dataset=NAMQ_10_GDP&v=Geopolitical entity (reporting)"
|
|
- "&h=TIME&from=2010-02-01&to=2018-10-01&GEO=[AL,AT,BE,BA,"
|
|
- "BG,HR,CY,CZ,DK,EE,EA19,FI,FR,DE,EL,HU,IS,IE,IT,XK,LV,LT,"
|
|
- "LU,MT,ME,NL,MK,NO,PL,PT,RO,RS,SK,SI,ES,SE,CH,TR,UK]"
|
|
- "&NA_ITEM=[B1GQ]&S_ADJ=[SCA]&UNIT=[CLV10_MNAC]"
|
|
+ "&".join(
|
|
+ [
|
|
+ "dataset=RBI_BULLETIN",
|
|
+ "v=TIME",
|
|
+ "h=Indicator",
|
|
+ "from=2022-01-01",
|
|
+ "to=2022-07-01",
|
|
+ ]
|
|
),
|
|
"econdb",
|
|
+ start="2020-01-01",
|
|
+ end="2022-01-01",
|
|
)
|
|
assert df.index[0].year == 2010
|
|
assert df.index[-1].year == 2018
|
|
Index: pandas-datareader-0.10.0/setup.cfg
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/setup.cfg
|
|
+++ /dev/null
|
|
@@ -1,48 +0,0 @@
|
|
-[versioneer]
|
|
-vcs = git
|
|
-style = pep440
|
|
-versionfile_source = pandas_datareader/_version.py
|
|
-versionfile_build = pandas_datareader/_version.py
|
|
-tag_prefix = v
|
|
-
|
|
-[isort]
|
|
-known_compat = pandas_datareader.compat.*
|
|
-sections = FUTURE,COMPAT,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
|
-known_first_party = pandas_datareader
|
|
-known_third_party = numpy,pandas,pytest,requests
|
|
-combine_as_imports = True
|
|
-force_sort_within_sections = True
|
|
-profile = black
|
|
-
|
|
-[tool:pytest]
|
|
-minversion = 5.0.1
|
|
-testpaths = pandas_datareader
|
|
-markers =
|
|
- stable: mark a test as applying to a stable reader
|
|
- requires_api_key: mark a test as requiring an API key
|
|
- alpha_vantage: mark a test of the AlphaVantage reader
|
|
- quandl: mark a test of the Quandl reader
|
|
-filterwarnings =
|
|
- ignore:`np.bool` is a deprecated alias:DeprecationWarning:pandas.core.indexes
|
|
- ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.core.indexes
|
|
- ignore:`np.float` is a deprecated alias:DeprecationWarning:pandas.core.indexes
|
|
- ignore:`np.complex` is a deprecated alias:DeprecationWarning:pandas.core.indexes
|
|
- ignore:`np.bool` is a deprecated alias:DeprecationWarning:pandas.core.internals.blocks
|
|
- ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.core.internals.blocks
|
|
- ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.core.internals.construction
|
|
- ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.io.parsers
|
|
- ignore:`np.object` is a deprecated alias:DeprecationWarning:pandas.core.dtypes.cast
|
|
- ignore:`np.float` is a deprecated alias:DeprecationWarning:pandas.core.internals.blocks
|
|
- ignore:`np.complex` is a deprecated alias:DeprecationWarning:pandas.core.internals.blocks
|
|
- ignore:Converting `np.inexact` or `np.floating` to a dtype:DeprecationWarning:pandas.core.indexes
|
|
-
|
|
-[flake8]
|
|
-ignore = E203, E266, E501, W503
|
|
-max-line-length = 80
|
|
-max-complexity = 18
|
|
-select = B,C,E,F,W,T4,B9
|
|
-
|
|
-[egg_info]
|
|
-tag_build =
|
|
-tag_date = 0
|
|
-
|
|
Index: pandas-datareader-0.10.0/setup.py
|
|
===================================================================
|
|
--- pandas-datareader-0.10.0.orig/setup.py
|
|
+++ /dev/null
|
|
@@ -1,52 +0,0 @@
|
|
-#!/usr/bin/env python
|
|
-# -*- coding: utf-8 -*-
|
|
-
|
|
-from setuptools import find_packages, setup
|
|
-
|
|
-import versioneer
|
|
-
|
|
-NAME = "pandas-datareader"
|
|
-
|
|
-
|
|
-def readme():
|
|
- with open("README.md") as f:
|
|
- return f.read()
|
|
-
|
|
-
|
|
-install_requires = []
|
|
-with open("./requirements.txt") as f:
|
|
- install_requires = f.read().splitlines()
|
|
-with open("./requirements-dev.txt") as f:
|
|
- tests_require = f.read().splitlines()
|
|
-
|
|
-setup(
|
|
- name=NAME,
|
|
- version=versioneer.get_version(),
|
|
- cmdclass=versioneer.get_cmdclass(),
|
|
- description="Data readers extracted from the pandas codebase,"
|
|
- "should be compatible with recent pandas versions",
|
|
- long_description=readme(),
|
|
- license="BSD License",
|
|
- author="The PyData Development Team",
|
|
- author_email="pydata@googlegroups.com",
|
|
- url="https://github.com/pydata/pandas-datareader",
|
|
- classifiers=[
|
|
- "Development Status :: 4 - Beta",
|
|
- "Environment :: Console",
|
|
- "Intended Audience :: Science/Research",
|
|
- "Operating System :: OS Independent",
|
|
- "Programming Language :: Python",
|
|
- "Programming Language :: Python :: 3",
|
|
- "Programming Language :: Python :: 3.6",
|
|
- "Programming Language :: Python :: 3.7",
|
|
- "Programming Language :: Python :: 3.8",
|
|
- "Topic :: Scientific/Engineering",
|
|
- ],
|
|
- keywords="data",
|
|
- install_requires=install_requires,
|
|
- packages=find_packages(exclude=["contrib", "docs", "tests*"]),
|
|
- test_suite="tests",
|
|
- tests_require=tests_require,
|
|
- zip_safe=False,
|
|
- python_requires=">=3.6",
|
|
-)
|