5

The title is self-explanatory. Is there a way to downgrade the conda packages to the ones that were the latest on a certain date?

pedrolucas
  • 53
  • 2

2 Answers2

4

This is not possible programmatically. Packages in Conda are specified through MatchSpec, which does not currently have any way to constrain on a build timestamp.

Manual Searching

When searching for packages via conda search, the --info flag will print the build timestamps if they are available. So, for example, if one wanted to find the latest version of PyMC3 that someone with Python 3.6 was running a year ago (9 Dec 2018), one could check

conda search --info 'conda-forge::pymc3'

and see that version 3.5, build py36_1000 would satisfy this. If one wanted to create an env with this build in it, they could use

conda create -n py36_pymc35 -c conda-forge pymc3=3.5=py36_1000
merv
  • 67,214
  • 13
  • 180
  • 245
1

2023 Update

In addition to Merv's post, I may add that the --json flag makes it actually quite easy to programmatically gather the history. Once you have the history, you can search for the latest package versions as of some date, and make an environment with them (we do that routinely to establish "low watermark" environments for our CIs).

The conda command line invocation is:

f'conda search -q {package} --info --json`

Here is some code that uses that to gather the history of a few packages. It is also multi-threaded to speed up things a little.

import io
import json
import subprocess
import yaml

from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta
from tqdm import tqdm

def shell(cmd):
    proc = subprocess.run(cmd, shell=True, capture_output=True)
    return proc.stdout.decode('utf-8')

def version_as_tuple(v):
    return tuple(map(int, v.split('.')))

def get_history(p):
    txt = shell(f"conda search -q {p} --info --json")
    d = json.loads(txt)
    h = defaultdict(set)
    for vv in d.values():
        for x in vv:
            h[version_as_tuple(x['version'])].add(
                datetime.fromtimestamp(x.get('timestamp', 0) / 1e3)
            )
    h = {vers: min(dates) for vers, dates in h.items()}
    return p, h

Example usage:

metayaml = """
    - boto3
    - pandas >=0.25
    - python >=3.8
"""

reqs = yaml.safe_load(metayaml)  # in real life, read from conda.recipe/meta.yaml
all_pkgs = sorted(set([p.split()[0] for p in reqs]))

with ThreadPoolExecutor() as pool:
    history = dict(tqdm(pool.map(get_history, all_pkgs), total=len(all_pkgs)))

After that, we have a neat version history for all dependent packages. For example:

>>> {v: f'{t:%Y-%m-%d}' for v, t in history['pandas'].items()}
{(0, 20, 3): '2017-09-18',
 (0, 21, 0): '2017-11-06',
 (0, 21, 1): '2017-12-12',
 ...
 (1, 4, 4): '2022-09-21',
 (1, 5, 1): '2022-11-16',
 (1, 5, 2): '2022-12-07'}

And:

asof = datetime.now() - timedelta(weeks=2*52)

new = {
    name: max([(vers, t) for vers, t in v.items() if t < asof])
    for name, v in history.items()
}
print(f'# as of {asof:%Y-%m-%d}')
for name, (vers, t) in new.items():
    print(f'  - {name} =={".".join(map(str, vers))} # released on {t:%Y-%m-%d}')

Which produces:

# as of 2021-01-20
  - boto3 ==1.16.55 # released on 2021-01-15
  - pandas ==1.2.0 # released on 2020-12-26
  - python ==3.9.1 # released on 2020-12-11
Pierre D
  • 24,012
  • 7
  • 60
  • 96