Usage of conda env export

This commit is contained in:
romainx
2020-03-03 21:11:33 +01:00
parent b46fa3f9e6
commit 416dc99d03

View File

@@ -26,7 +26,7 @@ import re
from collections import defaultdict from collections import defaultdict
from itertools import chain from itertools import chain
import logging import logging
import ast import json
from tabulate import tabulate from tabulate import tabulate
@@ -53,30 +53,65 @@ class CondaPackageHelper:
tty=True, command=["start.sh", "bash", "-c", "sleep infinity"] tty=True, command=["start.sh", "bash", "-c", "sleep infinity"]
) )
@staticmethod
def _conda_export_command(from_history=False):
"""Return the conda export command with or without history"""
cmd = ["conda", "env", "export", "-n", "base", "--json", "--no-builds"]
if from_history:
cmd.append("--from-history")
return cmd
def installed_packages(self): def installed_packages(self):
"""Return the installed packages""" """Return the installed packages"""
if self.installed is None: if self.installed is None:
LOGGER.info(f"Grabing the list of installed packages ...") LOGGER.info(f"Grabing the list of installed packages ...")
self.installed = self._packages(["conda", "list"]) self.installed = CondaPackageHelper._packages_from_json(
self._execute_command(CondaPackageHelper._conda_export_command())
)
return self.installed return self.installed
def specified_packages(self):
"""Return the specifications (i.e. packages installation requested)"""
if self.specs is None:
LOGGER.info(f"Grabing the list of specifications ...")
self.specs = CondaPackageHelper._packages_from_json(
self._execute_command(CondaPackageHelper._conda_export_command(True))
)
return self.specs
def _execute_command(self, command):
"""Execute a command on a running container"""
rc = self.running_container.exec_run(command)
return rc.output.decode("utf-8")
@staticmethod
def _packages_from_json(env_export):
"""Extract packages and versions from the lines returned by the list of specifications"""
dependencies = json.loads(env_export).get("dependencies")
packages_list = map(lambda x: x.split("=", 1), dependencies)
# TODO: could be improved
return {package[0]: set(package[1:]) for package in packages_list}
def available_packages(self): def available_packages(self):
"""Return the available packages""" """Return the available packages"""
if self.available is None: if self.available is None:
LOGGER.info( LOGGER.info(
f"Grabing the list of available packages (can take a while) ..." f"Grabing the list of available packages (can take a while) ..."
) )
self.available = self._packages(["conda", "search", "--outdated"]) # Keeping command line output since `conda search --outdated --json` is way too long ...
self.available = CondaPackageHelper._extract_available(
self._execute_command(["conda", "search", "--outdated"])
)
return self.available return self.available
def specified_packages(self): @staticmethod
"""Return the specifications (i.e. packages installation requested)""" def _extract_available(lines):
if self.specs is None: """Extract packages and versions from the lines returned by the list of packages"""
LOGGER.info(f"Grabing the list of specifications ...") ddict = defaultdict(set)
self.specs = self._specifications( for line in lines.splitlines()[2:]:
["grep", "^# update specs:", "/opt/conda/conda-meta/history"] pkg, version = re.match(r"^(\S+)\s+(\S+)", line, re.MULTILINE).groups()
) ddict[pkg].add(version)
return self.specs return ddict
def check_updatable_packages(self, specifications_only=True): def check_updatable_packages(self, specifications_only=True):
"""Check the updatables packages including or not dependencies""" """Check the updatables packages including or not dependencies"""
@@ -102,58 +137,6 @@ class CondaPackageHelper:
) )
return self.comparison return self.comparison
def get_outdated_summary(self, specifications_only=True):
"""Return a summary of outdated packages"""
if specifications_only:
nb_packages = len(self.specs)
else:
nb_packages = len(self.installed)
nb_updatable = len(self.comparison)
updatable_ratio = nb_updatable / nb_packages
return f"{nb_updatable}/{nb_packages} ({updatable_ratio:.0%}) packages could be updated"
def get_outdated_table(self):
"""Return a table of outdated packages"""
return tabulate(self.comparison, headers="keys")
def _execute_command(self, command):
"""Execute a command on a running container"""
rc = self.running_container.exec_run(command)
return rc.output.decode("utf-8")
def _specifications(self, command):
"""Return the list of specifications from a command"""
specifications = CondaPackageHelper._extract_specifications(
self._execute_command(command)
)
LOGGER.debug(f"List of specifications and versions {specifications}")
return specifications
def _packages(self, command):
"""Return the list of packages from a command"""
packages = CondaPackageHelper._extract_packages(self._execute_command(command))
LOGGER.debug(f"List of packages and versions {packages}")
return packages
@staticmethod
def _extract_packages(lines):
"""Extract packages and versions from the lines returned by the list of packages"""
ddict = defaultdict(set)
for line in lines.splitlines()[2:]:
pkg, version = re.match(r"^(\S+)\s+(\S+)", line, re.MULTILINE).groups()
ddict[pkg].add(version)
return ddict
@staticmethod
def _extract_specifications(lines):
"""Extract packages and versions from the lines returned by the list of specifications"""
packages_list = list()
for spec in (spec for spec in lines.split("\n") if spec.strip() != ""):
spec_list = ast.literal_eval(spec.split(": ")[1])
packages_list += map(lambda x: x.split("=", 1), spec_list)
# TODO: could be improved
return {package[0]: set(package[1:]) for package in packages_list}
@staticmethod @staticmethod
def semantic_cmp(version_string): def semantic_cmp(version_string):
"""Manage semantic versioning for comparison""" """Manage semantic versioning for comparison"""
@@ -177,3 +160,17 @@ class CondaPackageHelper:
mss = list(chain(*mysplit(version_string))) mss = list(chain(*mysplit(version_string)))
return tuple(map(try_int, mss)) return tuple(map(try_int, mss))
def get_outdated_summary(self, specifications_only=True):
"""Return a summary of outdated packages"""
if specifications_only:
nb_packages = len(self.specs)
else:
nb_packages = len(self.installed)
nb_updatable = len(self.comparison)
updatable_ratio = nb_updatable / nb_packages
return f"{nb_updatable}/{nb_packages} ({updatable_ratio:.0%}) packages could be updated"
def get_outdated_table(self):
"""Return a table of outdated packages"""
return tabulate(self.comparison, headers="keys")