Skip to content

Commit 6fb272c

Browse files
crusaderkyJoe Hamman
authored andcommitted
Rolling minimum dependency versions policy (#3358)
* - Downgrade numpy to 1.14, pandas to 0.20, scipy to 0.19 (24 months old) - Downgrade dask to 1.1 (6 months old) - Don't pin patch versions * Apply rolling policy (see #3222) * Automated tool to verify the minimum versions * Drop Python 3.5 * lint * Trivial cosmetic * Cosmetic * (temp) debug CI failure * Parallelize versions check script * Remove hacks for legacy dask * Documentation * Assorted cleanup * Assorted cleanup * Fix regression * Cleanup * type annotations upgraded to Python 3.6 * count_not_none backport * pd.Index.equals on legacy pandas returned False when comparing vs. a ndarray * Documentation * pathlib cleanup * Slide deprecations from 0.14 to 0.15 * More cleanups * More cleanups * Fix min_deps_check * Fix min_deps_check * Set policy of 12 months for pandas and scipy * Cleanup * Cleanup * Sphinx fix * Overhaul readthedocs environment * Fix test crash * Fix test crash * Prune readthedocs environment * Cleanup * Hack around versioneer bug on readthedocs CI * Code review * Prevent random timeouts in the readthedocs CI * What's New polish * Merge from Master * Trivial cosmetic * Reimplement pandas.core.common.count_not_none
1 parent 3e2a754 commit 6fb272c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+633
-1281
lines changed

azure-pipelines.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ jobs:
88
- job: Linux
99
strategy:
1010
matrix:
11-
py35-bare-minimum:
12-
conda_env: py35-bare-minimum
11+
py36-bare-minimum:
12+
conda_env: py36-bare-minimum
1313
py36-min-all-deps:
1414
conda_env: py36-min-all-deps
1515
py36-min-nep18:
@@ -82,13 +82,29 @@ jobs:
8282
mypy .
8383
displayName: mypy type checks
8484
85+
- job: MinimumVersionsPolicy
86+
pool:
87+
vmImage: 'ubuntu-16.04'
88+
steps:
89+
- template: ci/azure/add-conda-to-path.yml
90+
- bash: |
91+
conda install -y pyyaml
92+
python ci/min_deps_check.py ci/requirements/py36-bare-minimum.yml
93+
python ci/min_deps_check.py ci/requirements/py36-min-all-deps.yml
94+
displayName: minimum versions policy
95+
8596
- job: Docs
8697
pool:
8798
vmImage: 'ubuntu-16.04'
8899
steps:
89100
- template: ci/azure/install.yml
90101
parameters:
91-
env_file: doc/environment.yml
102+
env_file: ci/requirements/doc.yml
103+
- bash: |
104+
source activate xarray-tests
105+
# Replicate the exact environment created by the readthedocs CI
106+
conda install --yes --quiet -c pkgs/main mock pillow sphinx sphinx_rtd_theme
107+
displayName: Replicate readthedocs CI environment
92108
- bash: |
93109
source activate xarray-tests
94110
cd doc

ci/min_deps_check.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
"""Fetch from conda database all available versions of the xarray dependencies and their
2+
publication date. Compare it against requirements/py36-min-all-deps.yml to verify the
3+
policy on obsolete dependencies is being followed. Print a pretty report :)
4+
"""
5+
import subprocess
6+
import sys
7+
from concurrent.futures import ThreadPoolExecutor
8+
from datetime import datetime, timedelta
9+
from typing import Dict, Iterator, Tuple
10+
11+
import yaml
12+
13+
IGNORE_DEPS = {
14+
"black",
15+
"coveralls",
16+
"flake8",
17+
"hypothesis",
18+
"mypy",
19+
"pip",
20+
"pytest",
21+
"pytest-cov",
22+
"pytest-env",
23+
}
24+
25+
POLICY_MONTHS = {"python": 42, "numpy": 24, "pandas": 12, "scipy": 12}
26+
POLICY_MONTHS_DEFAULT = 6
27+
28+
has_errors = False
29+
30+
31+
def error(msg: str) -> None:
32+
global has_errors
33+
has_errors = True
34+
print("ERROR:", msg)
35+
36+
37+
def parse_requirements(fname) -> Iterator[Tuple[str, int, int]]:
38+
"""Load requirements/py36-min-all-deps.yml
39+
40+
Yield (package name, major version, minor version)
41+
"""
42+
global has_errors
43+
44+
with open(fname) as fh:
45+
contents = yaml.safe_load(fh)
46+
for row in contents["dependencies"]:
47+
if isinstance(row, dict) and list(row) == ["pip"]:
48+
continue
49+
pkg, eq, version = row.partition("=")
50+
if pkg.rstrip("<>") in IGNORE_DEPS:
51+
continue
52+
if pkg.endswith("<") or pkg.endswith(">") or eq != "=":
53+
error("package should be pinned with exact version: " + row)
54+
continue
55+
try:
56+
major, minor = version.split(".")
57+
except ValueError:
58+
error("expected major.minor (without patch): " + row)
59+
continue
60+
try:
61+
yield pkg, int(major), int(minor)
62+
except ValueError:
63+
error("failed to parse version: " + row)
64+
65+
66+
def query_conda(pkg: str) -> Dict[Tuple[int, int], datetime]:
67+
"""Query the conda repository for a specific package
68+
69+
Return map of {(major version, minor version): publication date}
70+
"""
71+
stdout = subprocess.check_output(
72+
["conda", "search", pkg, "--info", "-c", "defaults", "-c", "conda-forge"]
73+
)
74+
out = {} # type: Dict[Tuple[int, int], datetime]
75+
major = None
76+
minor = None
77+
78+
for row in stdout.decode("utf-8").splitlines():
79+
label, _, value = row.partition(":")
80+
label = label.strip()
81+
if label == "file name":
82+
value = value.strip()[len(pkg) :]
83+
major, minor = value.split("-")[1].split(".")[:2]
84+
major = int(major)
85+
minor = int(minor)
86+
if label == "timestamp":
87+
assert major is not None
88+
assert minor is not None
89+
ts = datetime.strptime(value.split()[0].strip(), "%Y-%m-%d")
90+
91+
if (major, minor) in out:
92+
out[major, minor] = min(out[major, minor], ts)
93+
else:
94+
out[major, minor] = ts
95+
96+
# Hardcoded fix to work around incorrect dates in conda
97+
if pkg == "python":
98+
out.update(
99+
{
100+
(2, 7): datetime(2010, 6, 3),
101+
(3, 5): datetime(2015, 9, 13),
102+
(3, 6): datetime(2016, 12, 23),
103+
(3, 7): datetime(2018, 6, 27),
104+
(3, 8): datetime(2019, 10, 14),
105+
}
106+
)
107+
108+
return out
109+
110+
111+
def process_pkg(
112+
pkg: str, req_major: int, req_minor: int
113+
) -> Tuple[str, int, int, str, int, int, str, str]:
114+
"""Compare package version from requirements file to available versions in conda.
115+
Return row to build pandas dataframe:
116+
117+
- package name
118+
- major version in requirements file
119+
- minor version in requirements file
120+
- publication date of version in requirements file (YYYY-MM-DD)
121+
- major version suggested by policy
122+
- minor version suggested by policy
123+
- publication date of version suggested by policy (YYYY-MM-DD)
124+
- status ("<", "=", "> (!)")
125+
"""
126+
print("Analyzing %s..." % pkg)
127+
versions = query_conda(pkg)
128+
129+
try:
130+
req_published = versions[req_major, req_minor]
131+
except KeyError:
132+
error("not found in conda: " + pkg)
133+
return pkg, req_major, req_minor, "-", 0, 0, "-", "(!)"
134+
135+
policy_months = POLICY_MONTHS.get(pkg, POLICY_MONTHS_DEFAULT)
136+
policy_published = datetime.now() - timedelta(days=policy_months * 30)
137+
138+
policy_major = req_major
139+
policy_minor = req_minor
140+
policy_published_actual = req_published
141+
for (major, minor), published in reversed(sorted(versions.items())):
142+
if published < policy_published:
143+
break
144+
policy_major = major
145+
policy_minor = minor
146+
policy_published_actual = published
147+
148+
if (req_major, req_minor) < (policy_major, policy_minor):
149+
status = "<"
150+
elif (req_major, req_minor) > (policy_major, policy_minor):
151+
status = "> (!)"
152+
error("Package is too new: " + pkg)
153+
else:
154+
status = "="
155+
156+
return (
157+
pkg,
158+
req_major,
159+
req_minor,
160+
req_published.strftime("%Y-%m-%d"),
161+
policy_major,
162+
policy_minor,
163+
policy_published_actual.strftime("%Y-%m-%d"),
164+
status,
165+
)
166+
167+
168+
def main() -> None:
169+
fname = sys.argv[1]
170+
with ThreadPoolExecutor(8) as ex:
171+
futures = [
172+
ex.submit(process_pkg, pkg, major, minor)
173+
for pkg, major, minor in parse_requirements(fname)
174+
]
175+
rows = [f.result() for f in futures]
176+
177+
print("Package Required Policy Status")
178+
print("------------- ----------------- ----------------- ------")
179+
fmt = "{:13} {:>1d}.{:<2d} ({:10}) {:>1d}.{:<2d} ({:10}) {}"
180+
for row in rows:
181+
print(fmt.format(*row))
182+
183+
assert not has_errors
184+
185+
186+
if __name__ == "__main__":
187+
main()

ci/requirements/doc.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: xarray-docs
2+
channels:
3+
# Don't change to pkgs/main, as it causes random timeouts in readthedocs
4+
- conda-forge
5+
dependencies:
6+
- python=3.7
7+
- bottleneck
8+
- cartopy
9+
- h5netcdf
10+
- ipython
11+
- iris
12+
- netcdf4
13+
- numpy
14+
- numpydoc
15+
- pandas<0.25 # Hack around https://github.com/pydata/xarray/issues/3369
16+
- rasterio
17+
- seaborn
18+
- sphinx
19+
- sphinx-gallery
20+
- sphinx_rtd_theme
21+
- zarr

ci/requirements/py35-bare-minimum.yml

Lines changed: 0 additions & 15 deletions
This file was deleted.

ci/requirements/py36-bare-minimum.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: xarray-tests
2+
channels:
3+
- conda-forge
4+
dependencies:
5+
- python=3.6
6+
- coveralls
7+
- pytest
8+
- pytest-cov
9+
- pytest-env
10+
- numpy=1.14
11+
- pandas=0.24

ci/requirements/py36-min-all-deps.yml

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,47 @@ name: xarray-tests
22
channels:
33
- conda-forge
44
dependencies:
5-
- python=3.6.7
5+
# MINIMUM VERSIONS POLICY: see doc/installing.rst
6+
# Run ci/min_deps_check.py to verify that this file respects the policy.
7+
# When upgrading python, numpy, or pandas, must also change
8+
# doc/installing.rst and setup.py.
9+
- python=3.6
610
- black
7-
- boto3=1.9.235
8-
- bottleneck=1.2.1
9-
- cdms2=3.1.3
10-
- cfgrib=0.9.7.2
11-
- cftime=1.0.3.4
11+
- boto3=1.9
12+
- bottleneck=1.2
13+
- cartopy=0.17
14+
- cdms2=3.1
15+
- cfgrib=0.9
16+
- cftime=1.0
1217
- coveralls
13-
- dask=2.4.0
14-
- distributed=2.4.0
18+
- dask=1.2
19+
- distributed=1.27
1520
- flake8
16-
- h5netcdf=0.7.4
17-
- h5py=2.10.0
18-
- hdf5=1.10.5
21+
- h5netcdf=0.7
22+
- h5py=2.9 # Policy allows for 2.10, but it's a conflict-fest
23+
- hdf5=1.10
1924
- hypothesis
20-
- iris=2.2.0
21-
- lxml=4.4.1 # optional dep of pydap
22-
- matplotlib=3.1.1
23-
- mypy==0.730 # Must match .pre-commit-config.yaml
24-
- nc-time-axis=1.2.0
25-
- netcdf4=1.5.1.2
26-
- numba=0.45.1
27-
- numpy=1.17.2
28-
- pandas=0.25.1
25+
- iris=2.2
26+
- lxml=4.4 # Optional dep of pydap
27+
- matplotlib=3.1
28+
- mypy=0.730 # Must match .pre-commit-config.yaml
29+
- nc-time-axis=1.2
30+
- netcdf4=1.4
31+
- numba=0.44
32+
- numpy=1.14
33+
- pandas=0.24
2934
- pip
30-
- pseudonetcdf=3.0.2
31-
- pydap=3.2.2
32-
- pynio=1.5.5
35+
- pseudonetcdf=3.0
36+
- pydap=3.2
37+
- pynio=1.5
3338
- pytest
3439
- pytest-cov
3540
- pytest-env
36-
- rasterio=1.0.28
37-
- scipy=1.3.1
38-
- seaborn=0.9.0
41+
- rasterio=1.0
42+
- scipy=1.0 # Policy allows for 1.2, but scipy>=1.1 breaks numpy=1.14
43+
- seaborn=0.9
3944
# - sparse # See py36-min-nep18.yml
40-
- toolz=0.10.0
41-
- zarr=2.3.2
45+
- toolz=0.10
46+
- zarr=2.3
4247
- pip:
4348
- numbagg==0.1

ci/requirements/py36-min-nep18.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ channels:
44
dependencies:
55
# Optional dependencies that require NEP18, such as sparse,
66
# require drastically newer packages than everything else
7-
- python=3.6.7
7+
- python=3.6
88
- coveralls
9-
- dask=2.4.0
10-
- distributed=2.4.0
9+
- dask=2.4
10+
- distributed=2.4
1111
- numpy=1.17
12-
- pandas=0.25
12+
- pandas=0.24
1313
- pytest
1414
- pytest-cov
1515
- pytest-env
16-
- scipy=1.3
17-
- sparse=0.8.0
16+
- scipy=1.2
17+
- sparse=0.8

0 commit comments

Comments
 (0)