Skip to content
Snippets Groups Projects
Commit da2c98a3 authored by Paul McCarthy's avatar Paul McCarthy :mountain_bicyclist:
Browse files

ENH,RF: Re-arrange code to incorporate update of externally hosted packages

parent d4ee2151
No related branches found
No related tags found
1 merge request!45New update_fsl_package script
...@@ -246,12 +246,10 @@ class Package: ...@@ -246,12 +246,10 @@ class Package:
@ft.lru_cache @ft.lru_cache
def query_installed_packages(include_external : bool) -> Dict[str, Package]: def query_installed_packages() -> Dict[str, Package]:
"""Uses conda to find out the versions of all packages installed in """Uses conda to find out the versions of all packages installed in
$FSLDIR, which are sourced from the FSL conda channels. $FSLDIR, and which are sourced from the FSL conda channels, or
which are listed in EXTERNALLY_HOSTED_PACKAGES.
If "include_external" is True, externally hosted packages that are listed
in EXTERNALLY_HOSTED_PACKAGES are also queried.
Returns a dict of {pkgname : Package} mappings. The "dependencies" Returns a dict of {pkgname : Package} mappings. The "dependencies"
attributes of the package objects are not populated. attributes of the package objects are not populated.
...@@ -277,7 +275,7 @@ def query_installed_packages(include_external : bool) -> Dict[str, Package]: ...@@ -277,7 +275,7 @@ def query_installed_packages(include_external : bool) -> Dict[str, Package]:
internal = pkg['base_url'].rstrip() in channels internal = pkg['base_url'].rstrip() in channels
external = pkg['name'] in EXTERNALLY_HOSTED_PACKAGES external = pkg['name'] in EXTERNALLY_HOSTED_PACKAGES
if internal or (external and include_external): if internal or external:
pkgs[pkg['name']] = Package(pkg['name'], pkgs[pkg['name']] = Package(pkg['name'],
pkg['version'], pkg['version'],
pkg['channel'], pkg['channel'],
...@@ -286,26 +284,35 @@ def query_installed_packages(include_external : bool) -> Dict[str, Package]: ...@@ -286,26 +284,35 @@ def query_installed_packages(include_external : bool) -> Dict[str, Package]:
@ft.lru_cache @ft.lru_cache
def download_external_package_metadata(pkgname : str, def download_package_metadata(pkgname : str,
channel_url : str, channel : str,
development : bool) -> Optional[Package]: development : bool) -> Optional[Package]:
"""Downloads metadata about one externally hosted package. The returned """Downloads metadata about one externally hosted package. The returned
Package object does not contain platform or dependency information. Package object does not contain information about dependencies.
Returns None if the package does not appear to be hosted on the channel,
or if there are no suitable versions available for the host platform.
pkgname: Name of package to lookup
channel: Name of channel on anaconda.org. Can also be a full channel
URL
development: Whether to consider development versions of packages
""" """
# if channel_url is a full url, we # if we've been given a full url, we
# download the full channel metdata # download the full channel metdata
# and look up the package # and look up the package. This is
if any(channel_url.startswith(p) for p in ('https:', 'http:', 'file:')): # expensive if downloadintg from
# anaconda.org
if any(channel.startswith(p) for p in ('https:', 'http:', 'file:')):
chandata = download_channel_metadata(channel_url) chandata = download_channel_metadata(channel_url)
pkgs = identify_packages([chandata], [pkgname], development) pkgs = identify_packages([chandata], [pkgname], development)
return pkgs.get(pkgname, [None])[-1] return pkgs.get(pkgname, [None])[-1]
# Othrerwise channel_url is the name # Otherwise channel_url is the name of
# of an anaconda.org channel - we # an anaconda.org channel - we just
# just retrieve information about # retrieve information about the one
# the package # package using the HTTP API.
channel = channel_url
api_url = f'https://api.anaconda.org/package/{channel}/' api_url = f'https://api.anaconda.org/package/{channel}/'
channel_url = f'https://anaconda.org/{channel}/' channel_url = f'https://anaconda.org/{channel}/'
...@@ -385,19 +392,17 @@ def download_channel_metadata(channel_url : str, **kwargs) -> Tuple[Dict, Dict]: ...@@ -385,19 +392,17 @@ def download_channel_metadata(channel_url : str, **kwargs) -> Tuple[Dict, Dict]:
return chandata, platdata return chandata, platdata
def identify_packages( def parse_channel_metadata(
channeldata : List[Tuple[Dict, Dict]], channeldata : List[Tuple[Dict, Dict]],
pkgnames : Sequence[str], pkgnames : Sequence[str],
development : bool development : bool
) -> Dict[str, List[Package]]: ) -> Dict[str, Package]:
"""Return metadata about the requested packages. """Extract metadata about the requested packages from the channel metadata.
Loads channel and platform metadata from the conda channels. Parses the Parses the channel metadata, and creates a Package object for every
metadata, and creates a Package object for every requested package. requested package.
Returns a dict of {name : [Package]} mappings, where each entry contains Returns a dict of {name : Package} mappings.
Package objects for all available versions of the packae, ordered from
oldest (first) to newest (last).
channeldata: Sequence of channel data from one or more conda channels, as channeldata: Sequence of channel data from one or more conda channels, as
returned by the download_channel_metadata function. returned by the download_channel_metadata function.
...@@ -414,13 +419,18 @@ def identify_packages( ...@@ -414,13 +419,18 @@ def identify_packages(
if pkgname in cdata['packages']: if pkgname in cdata['packages']:
pkgchannels[pkgname] = (cdata, pdata) pkgchannels[pkgname] = (cdata, pdata)
break break
# This package is not available # This package is not present in
# the provided channel metadata
else: else:
log.debug(f'Package {pkgname} is not available - ignoring.') log.debug(f'Package {pkgname} is not available - ignoring.')
continue continue
# Create Package objects for every available version of # Create Package objects for every available version of
# the requested packages. The packages dict has structure # the requested packages. Information about available
# versions is not necessarily sorted, so we have to
# parse and sort every entry to find the most recent.
#
# The packages dict has structure
# #
# {pkgname : [Package, Package, ...]} # {pkgname : [Package, Package, ...]}
# #
...@@ -437,10 +447,12 @@ def identify_packages( ...@@ -437,10 +447,12 @@ def identify_packages(
continue continue
bisect.insort(packages[pkgname], pkg) bisect.insort(packages[pkgname], pkg)
return packages # After sorting from oldest->newest we can just
# return the newest version for each package
return {pkgname : pkgs[-1] for pkgname, pkgs in packages.items()}
def filter_packages(packages : Dict[str, List[Package]]) -> List[Package]: def filter_packages(packages : Dict[str, Package]) -> List[Package]:
"""Identifies the versions of packages that should be installed. """Identifies the versions of packages that should be installed.
Removes packages that are not installed, or that are already up to date. Removes packages that are not installed, or that are already up to date.
...@@ -451,7 +463,7 @@ def filter_packages(packages : Dict[str, List[Package]]) -> List[Package]: ...@@ -451,7 +463,7 @@ def filter_packages(packages : Dict[str, List[Package]]) -> List[Package]:
filtered = [] filtered = []
for pkgname, pkgs in packages.items(): for pkgname, pkg in packages.items():
# Find the Package object corresponding # Find the Package object corresponding
# to the installed version # to the installed version
...@@ -461,11 +473,6 @@ def filter_packages(packages : Dict[str, List[Package]]) -> List[Package]: ...@@ -461,11 +473,6 @@ def filter_packages(packages : Dict[str, List[Package]]) -> List[Package]:
log.debug(f'Package {pkgname} is not installed - ignoring') log.debug(f'Package {pkgname} is not installed - ignoring')
continue continue
# select the newest available
# version of the package as
# the installation candidate
pkg = pkgs[-1]
if pkg <= installed: if pkg <= installed:
log.debug(f'{pkg.name} is already up to date (available: ' log.debug(f'{pkg.name} is already up to date (available: '
f'{pkg.version}, installed: {installed.version}) ' f'{pkg.version}, installed: {installed.version}) '
...@@ -535,6 +542,8 @@ def parse_args(argv : Optional[Sequence[str]]) -> argparse.Namespace: ...@@ -535,6 +542,8 @@ def parse_args(argv : Optional[Sequence[str]]) -> argparse.Namespace:
help='Install package[s] without prompting for confirmation') help='Install package[s] without prompting for confirmation')
parser.add_argument('-a', '--all', action='store_true', parser.add_argument('-a', '--all', action='store_true',
help='Update all installed FSL packages') help='Update all installed FSL packages')
parser.add_argument('-e', '--external', action='store_true',
help='Consider externally hosted packages')
parser.add_argument('--internal', help=argparse.SUPPRESS) parser.add_argument('--internal', help=argparse.SUPPRESS)
parser.add_argument('--username', help=argparse.SUPPRESS) parser.add_argument('--username', help=argparse.SUPPRESS)
...@@ -547,6 +556,11 @@ def parse_args(argv : Optional[Sequence[str]]) -> argparse.Namespace: ...@@ -547,6 +556,11 @@ def parse_args(argv : Optional[Sequence[str]]) -> argparse.Namespace:
parser.error('Specify at least one package, or use --all ' parser.error('Specify at least one package, or use --all '
'to update all installed FSL packages.') 'to update all installed FSL packages.')
# externally hosted package has been requested
if (len(args.package) > 0) and \
any(args.package in EXTERNALLY_HOSTED_PACKAGES):
args.external = True
return args return args
...@@ -568,10 +582,39 @@ def main(argv : Sequence[str] = None): ...@@ -568,10 +582,39 @@ def main(argv : Sequence[str] = None):
if args.verbose: log.setLevel(logging.DEBUG) if args.verbose: log.setLevel(logging.DEBUG)
else: log.setLevel(logging.INFO) else: log.setLevel(logging.INFO)
# Download information about all # Build a list of all packages
# available packages on the FSL # to consider for the update
# conda channels. print('Building FSL package list...')
print('Downloading FSL conda channel metadata ...') if args.all:
pkgnames = list(query_installed_packages().keys())
else:
pkgnames = args.package
# build a dict of {pkgname : Package}
# mappings for all candidate packages
packages = {}
# We start with externally hosted
# packages (e.g. from conda-forge),
# if requested
if args.external:
if args.all:
extpkgs = list(EXTERNALLY_HOSTED_PACKAGES.keys())
else:
extpkgs = [p for p in pkgnames if p in EXTERNALLY_HOSTED_PACKAGES]
print(f'Downloading externally hosted package metadata...')
for pkgname in extpkgs:
pkg = download_package_metadata(
pkgname,
EXTERNALLY_HOSTED_PACKAGES[pkgname],
args.development)
if pkg is not None:
packages[pkgname] = pkg
# Download information about all available
# packages on the FSL conda channels.
print('Downloading FSL conda channel metadata...')
channeldata = [download_channel_metadata(PUBLIC_FSL_CHANNEL)] channeldata = [download_channel_metadata(PUBLIC_FSL_CHANNEL)]
if args.internal: if args.internal:
channeldata.insert(0, download_channel_metadata( channeldata.insert(0, download_channel_metadata(
...@@ -579,16 +622,14 @@ def main(argv : Sequence[str] = None): ...@@ -579,16 +622,14 @@ def main(argv : Sequence[str] = None):
username=args.username, username=args.username,
password=args.password)) password=args.password))
print('Building FSL package list ...') # Extract metadata for all requested
if args.all: # packages from the FSL channel metadata
packages = list(query_installed_packages(args.external).keys()) packages.update(parse_channel_metadata(
else: channeldata,
packages = args.package pkgnames,
args.development))
# Identify the versions that are # Remove packages which are already up to date
# available for the packages the
# user has requested.
packages = identify_packages(channeldata, packages, args.development)
packages = filter_packages(packages) packages = filter_packages(packages)
if len(packages) == 0: if len(packages) == 0:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment