Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rolling minimum dependency versions policy #3358

Merged
merged 47 commits into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4c10106
- Downgrade numpy to 1.14, pandas to 0.20, scipy to 0.19 (24 months old)
crusaderky Sep 30, 2019
e3f6fb2
Merge remote-tracking branch 'upstream/master' into mindeps
Oct 1, 2019
24263b1
Apply rolling policy (see #3222)
Oct 1, 2019
9386f98
Automated tool to verify the minimum versions
Oct 1, 2019
9ca41cb
Drop Python 3.5
Oct 1, 2019
93c3c46
lint
Oct 1, 2019
f061f84
Trivial cosmetic
Oct 1, 2019
604074b
Cosmetic
Oct 2, 2019
b608375
(temp) debug CI failure
Oct 2, 2019
7a69eac
Parallelize versions check script
Oct 2, 2019
9cca367
Remove hacks for legacy dask
Oct 2, 2019
ba9ed69
Documentation
Oct 2, 2019
1400993
Assorted cleanup
Oct 2, 2019
187f951
Merge remote-tracking branch 'upstream/master' into mindeps
Oct 2, 2019
37a799f
Assorted cleanup
Oct 2, 2019
cbc7f74
Fix regression
Oct 2, 2019
98cf478
Cleanup
Oct 2, 2019
85f4c5b
type annotations upgraded to Python 3.6
Oct 2, 2019
8a28dbb
count_not_none backport
Oct 2, 2019
36c7754
pd.Index.equals on legacy pandas returned False when comparing vs. a …
Oct 2, 2019
c11abdd
Documentation
Oct 2, 2019
e0b4c84
pathlib cleanup
Oct 2, 2019
3ce92f3
Slide deprecations from 0.14 to 0.15
Oct 2, 2019
b0e0bf8
More cleanups
Oct 2, 2019
0249ec3
More cleanups
Oct 2, 2019
f7b7073
Fix min_deps_check
Oct 2, 2019
98422a9
Fix min_deps_check
Oct 2, 2019
8098349
Set policy of 12 months for pandas and scipy
Oct 3, 2019
5efaa09
Cleanup
Oct 3, 2019
02e7d2b
Cleanup
Oct 3, 2019
31f2308
Sphinx fix
Oct 3, 2019
7f9ce0b
Overhaul readthedocs environment
Oct 3, 2019
21f79e5
Fix test crash
Oct 3, 2019
dbd16cb
Merge remote-tracking branch 'upstream/master' into mindeps
Oct 3, 2019
a2020b6
Fix test crash
Oct 3, 2019
c94b587
Prune readthedocs environment
Oct 3, 2019
6d60700
Cleanup
Oct 3, 2019
bb5334f
Hack around versioneer bug on readthedocs CI
Oct 3, 2019
62f67a4
Code review
Oct 3, 2019
be7518c
Prevent random timeouts in the readthedocs CI
Oct 3, 2019
b5d7665
Merge remote-tracking branch 'upstream/master' into mindeps
Oct 3, 2019
0094689
What's New polish
Oct 3, 2019
397f48e
Merge remote-tracking branch 'upstream/master' into mindeps
crusaderky Oct 4, 2019
d25bf15
Merge from Master
crusaderky Oct 4, 2019
b463f30
Merge remote-tracking branch 'upstream/master' into mindeps
crusaderky Oct 4, 2019
bcfbae6
Trivial cosmetic
crusaderky Oct 4, 2019
b20349c
Reimplement pandas.core.common.count_not_none
crusaderky Oct 7, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ jobs:
- job: Linux
strategy:
matrix:
py35-bare-minimum:
conda_env: py35-bare-minimum
py36-bare-minimum:
conda_env: py36-bare-minimum
py36-min-all-deps:
conda_env: py36-min-all-deps
py36-min-nep18:
Expand Down Expand Up @@ -82,13 +82,24 @@ jobs:
mypy .
displayName: mypy type checks

- job: MinimumVersionsPolicy
pool:
vmImage: 'ubuntu-16.04'
steps:
- template: ci/azure/add-conda-to-path.yml
- bash: |
conda install -y pyyaml
python ci/min_deps_check.py ci/requirements/py36-bare-minimum.yml
python ci/min_deps_check.py ci/requirements/py36-min-all-deps.yml
displayName: minimum versions policy

- job: Docs
pool:
vmImage: 'ubuntu-16.04'
steps:
- template: ci/azure/install.yml
parameters:
env_file: doc/environment.yml
env_file: ci/requirements/doc.yml
- bash: |
source activate xarray-tests
cd doc
Expand Down
187 changes: 187 additions & 0 deletions ci/min_deps_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"""Fetch from conda database all available versions of the xarray dependencies and their
publication date. Compare it against requirements/py36-min-all-deps.yml to verify the
policy on obsolete dependencies is being followed. Print a pretty report :)
"""
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta
from typing import Dict, Iterator, Tuple

import yaml

IGNORE_DEPS = {
"black",
"coveralls",
"flake8",
"hypothesis",
"mypy",
"pip",
"pytest",
"pytest-cov",
"pytest-env",
}

POLICY_MONTHS = {"python": 42, "numpy": 24, "pandas": 12, "scipy": 12}
POLICY_MONTHS_DEFAULT = 6

has_errors = False


def error(msg: str) -> None:
global has_errors
has_errors = True
print("ERROR:", msg)


def parse_requirements(fname) -> Iterator[Tuple[str, int, int]]:
"""Load requirements/py36-min-all-deps.yml

Yield (package name, major version, minor version)
"""
global has_errors

with open(fname) as fh:
contents = yaml.safe_load(fh)
for row in contents["dependencies"]:
if isinstance(row, dict) and list(row) == ["pip"]:
continue
pkg, eq, version = row.partition("=")
if pkg.rstrip("<>") in IGNORE_DEPS:
continue
if pkg.endswith("<") or pkg.endswith(">") or eq != "=":
error("package should be pinned with exact version: " + row)
continue
try:
major, minor = version.split(".")
except ValueError:
error("expected major.minor (without patch): " + row)
continue
try:
yield pkg, int(major), int(minor)
except ValueError:
error("failed to parse version: " + row)


def query_conda(pkg: str) -> Dict[Tuple[int, int], datetime]:
"""Query the conda repository for a specific package

Return map of {(major version, minor version): publication date}
"""
stdout = subprocess.check_output(
["conda", "search", pkg, "--info", "-c", "defaults", "-c", "conda-forge"]
)
out = {} # type: Dict[Tuple[int, int], datetime]
major = None
minor = None

for row in stdout.decode("utf-8").splitlines():
label, _, value = row.partition(":")
label = label.strip()
if label == "file name":
value = value.strip()[len(pkg) :]
major, minor = value.split("-")[1].split(".")[:2]
major = int(major)
minor = int(minor)
if label == "timestamp":
assert major is not None
assert minor is not None
ts = datetime.strptime(value.split()[0].strip(), "%Y-%m-%d")

if (major, minor) in out:
out[major, minor] = min(out[major, minor], ts)
else:
out[major, minor] = ts

# Hardcoded fix to work around incorrect dates in conda
if pkg == "python":
out.update(
{
(2, 7): datetime(2010, 6, 3),
(3, 5): datetime(2015, 9, 13),
(3, 6): datetime(2016, 12, 23),
(3, 7): datetime(2018, 6, 27),
(3, 8): datetime(2019, 10, 14),
}
)

return out


def process_pkg(
pkg: str, req_major: int, req_minor: int
) -> Tuple[str, int, int, str, int, int, str, str]:
"""Compare package version from requirements file to available versions in conda.
Return row to build pandas dataframe:

- package name
- major version in requirements file
- minor version in requirements file
- publication date of version in requirements file (YYYY-MM-DD)
- major version suggested by policy
- minor version suggested by policy
- publication date of version suggested by policy (YYYY-MM-DD)
- status ("<", "=", "> (!)")
"""
print("Analyzing %s..." % pkg)
versions = query_conda(pkg)

try:
req_published = versions[req_major, req_minor]
except KeyError:
error("not found in conda: " + pkg)
return pkg, req_major, req_minor, "-", 0, 0, "-", "(!)"

policy_months = POLICY_MONTHS.get(pkg, POLICY_MONTHS_DEFAULT)
policy_published = datetime.now() - timedelta(days=policy_months * 30)

policy_major = req_major
policy_minor = req_minor
policy_published_actual = req_published
for (major, minor), published in reversed(sorted(versions.items())):
if published < policy_published:
break
policy_major = major
policy_minor = minor
policy_published_actual = published

if (req_major, req_minor) < (policy_major, policy_minor):
status = "<"
elif (req_major, req_minor) > (policy_major, policy_minor):
status = "> (!)"
error("Package is too new: " + pkg)
else:
status = "="

return (
pkg,
req_major,
req_minor,
req_published.strftime("%Y-%m-%d"),
policy_major,
policy_minor,
policy_published_actual.strftime("%Y-%m-%d"),
status,
)


def main() -> None:
fname = sys.argv[1]
with ThreadPoolExecutor(8) as ex:
futures = [
ex.submit(process_pkg, pkg, major, minor)
for pkg, major, minor in parse_requirements(fname)
]
rows = [f.result() for f in futures]

print("Package Required Policy Status")
print("------------- ----------------- ----------------- ------")
fmt = "{:13} {:>1d}.{:<2d} ({:10}) {:>1d}.{:<2d} ({:10}) {}"
for row in rows:
print(fmt.format(*row))

assert not has_errors


if __name__ == "__main__":
main()
23 changes: 23 additions & 0 deletions ci/requirements/doc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: xarray-docs
channels:
- pkgs/main # Keep the env identical to readthedocs CI
- conda-forge
dependencies:
- python=3.7
- bottleneck
- cartopy
- h5netcdf
- ipython
- iris
- mock # Keep the env identical to readthedocs CI
- netcdf4
- numpy
- numpydoc
- pandas
- pillow # Keep the env identical to readthedocs CI
- rasterio
- seaborn
- sphinx
- sphinx-gallery
- sphinx_rtd_theme
- zarr
15 changes: 0 additions & 15 deletions ci/requirements/py35-bare-minimum.yml

This file was deleted.

11 changes: 11 additions & 0 deletions ci/requirements/py36-bare-minimum.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: xarray-tests
channels:
- conda-forge
dependencies:
- python=3.6
- coveralls
- pytest
- pytest-cov
- pytest-env
- numpy=1.14
- pandas=0.24
61 changes: 33 additions & 28 deletions ci/requirements/py36-min-all-deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,47 @@ name: xarray-tests
channels:
- conda-forge
dependencies:
- python=3.6.7
# MINIMUM VERSIONS POLICY: see doc/installing.rst
# Run ci/min_deps_check.py to verify that this file respects the policy.
# When upgrading python, numpy, or pandas, must also change
# doc/installing.rst and setup.py.
- python=3.6
- black
- boto3=1.9.235
- bottleneck=1.2.1
- cdms2=3.1.3
- cfgrib=0.9.7.2
- cftime=1.0.3.4
- boto3=1.9
- bottleneck=1.2
- cartopy=0.17
- cdms2=3.1
- cfgrib=0.9
- cftime=1.0
- coveralls
- dask=2.4.0
- distributed=2.4.0
- dask=1.2
- distributed=1.27
- flake8
- h5netcdf=0.7.4
- h5py=2.10.0
- hdf5=1.10.5
- h5netcdf=0.7
- h5py=2.9 # Policy allows for 2.10, but it's a conflict-fest
- hdf5=1.10
- hypothesis
- iris=2.2.0
- lxml=4.4.1 # optional dep of pydap
- matplotlib=3.1.1
- mypy==0.720 # Must match .pre-commit-config.yaml
- nc-time-axis=1.2.0
- netcdf4=1.5.1.2
- numba=0.45.1
- numpy=1.17.2
- pandas=0.25.1
- iris=2.2
- lxml=4.4 # Optional dep of pydap
- matplotlib=3.1
- mypy=0.720 # Must match .pre-commit-config.yaml
- nc-time-axis=1.2
- netcdf4=1.4
- numba=0.44
- numpy=1.14
- pandas=0.24
- pip
- pseudonetcdf=3.0.2
- pydap=3.2.2
- pynio=1.5.5
- pseudonetcdf=3.0
- pydap=3.2
- pynio=1.5
- pytest
- pytest-cov
- pytest-env
- rasterio=1.0.28
- scipy=1.3.1
- seaborn=0.9.0
- rasterio=1.0
- scipy=1.0 # Policy allows for 1.2, but scipy>=1.1 breaks numpy=1.14
- seaborn=0.9
# - sparse # See py36-min-nep18.yml
- toolz=0.10.0
- zarr=2.3.2
- toolz=0.10
- zarr=2.3
- pip:
- numbagg==0.1
12 changes: 6 additions & 6 deletions ci/requirements/py36-min-nep18.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ channels:
dependencies:
# Optional dependencies that require NEP18, such as sparse,
# require drastically newer packages than everything else
- python=3.6.7
- python=3.6
- coveralls
- dask=2.4.0
- distributed=2.4.0
- dask=2.4
- distributed=2.4
- numpy=1.17
- pandas=0.25
- pandas=0.24
- pytest
- pytest-cov
- pytest-env
- scipy=1.3
- sparse=0.8.0
- scipy=1.2
- sparse=0.8
Loading