Source code for pyscaffold.update

# -*- coding: utf-8 -*-
"""
Functionality to update one PyScaffold version to another
"""
import os
from functools import reduce
from os.path import exists as path_exists
from os.path import join as join_path

from pkg_resources import parse_version

from . import __version__ as pyscaffold_version
from .contrib.configupdater import ConfigUpdater
from .log import logger
from .structure import FileOp
from .utils import get_id, get_setup_requires_version


[docs]def apply_update_rules(struct, opts, prefix=None): """Apply update rules using :obj:`~.FileOp` to a directory structure. As a result the filtered structure keeps only the files that actually will be written. Args: opts (dict): options of the project, containing the following flags: - **update**: when the project already exists and should be updated - **force**: overwrite all the files that already exist struct (dict): directory structure as dictionary of dictionaries (in this tree representation, each leaf can be just a string or a tuple also containing an update rule) prefix (str): prefix path for the structure Returns: tuple(dict, dict): directory structure with keys removed according to the rules (in this tree representation, all the leaves are strings) and input options """ if prefix is None: prefix = os.getcwd() filtered = {} for k, v in struct.items(): if isinstance(v, dict): v, _ = apply_update_rules(v, opts, join_path(prefix, k)) else: path = join_path(prefix, k) v = apply_update_rule_to_file(path, v, opts) if v is not None: filtered[k] = v return filtered, opts
[docs]def apply_update_rule_to_file(path, value, opts): """Applies the update rule to a given file path Args: path (str): file path value (tuple or str): content (and update rule) opts (dict): options of the project, containing the following flags: - **update**: if the project already exists and should be updated - **force**: overwrite all the files that already exist Returns: content of the file if it should be generated or None otherwise. """ if isinstance(value, (tuple, list)): content, rule = value else: content, rule = value, None update = opts.get('update') force = opts.get('force') skip = update and not force and ( rule == FileOp.NO_CREATE or path_exists(path) and rule == FileOp.NO_OVERWRITE) if skip: logger.report('skip', path) return None return content
[docs]def read_setupcfg(project_path): """Reads-in setup.cfg for updating Args: project_path (str): path to project Returns: """ path = join_path(project_path, 'setup.cfg') updater = ConfigUpdater() updater.read(path, encoding='utf-8') return updater
[docs]def invoke_action(action, struct, opts): """Invoke action with proper logging. Args: struct (dict): project representation as (possibly) nested :obj:`dict`. opts (dict): given options, see :obj:`create_project` for an extensive list. Returns: tuple(dict, dict): updated project representation and options """ logger.report('invoke', get_id(action)) with logger.indent(): struct, opts = action(struct, opts) return struct, opts
[docs]def get_curr_version(project_path): """Retrieves the PyScaffold version that put up the scaffold Args: project_path: path to project Returns: Version: version specifier """ setupcfg = read_setupcfg(project_path).to_dict() return parse_version(setupcfg['pyscaffold']['version'])
[docs]def version_migration(struct, opts): """Migrations from one version to another Args: struct (dict): previous directory structure (ignored) opts (dict): options of the project Returns: tuple(dict, dict): structure as dictionary of dictionaries and input options """ update = opts.get('update') if not update: return struct, opts curr_version = get_curr_version(opts['project']) # specify how to migrate from one version to another as ordered list migration_plans = [ (parse_version('3.1'), [add_entrypoints, add_setup_requires]) ] for plan_version, plan_actions in migration_plans: if curr_version < plan_version: struct, opts = reduce(lambda acc, f: invoke_action(f, *acc), plan_actions, (struct, opts)) # note the updating version in setup.cfg for future use update_pyscaffold_version(opts['project'], opts['pretend']) # replace the old version with the updated one opts['version'] = pyscaffold_version return struct, opts
[docs]def add_entrypoints(struct, opts): """Add [options.entry_points] to setup.cfg Args: struct (dict): previous directory structure (ignored) opts (dict): options of the project Returns: tuple(dict, dict): structure as dictionary of dictionaries and input options """ setupcfg = read_setupcfg(opts['project']) section_str = """[options.entry_points] # Add here console scripts like: # console_scripts = # script_name = ${package}.module:function # For example: # console_scripts = # fibonacci = ${package}.skeleton:run # And any other entry points, for example: # pyscaffold.cli = # awesome = pyscaffoldext.awesome.extension:AwesomeExtension """ new_section_name = 'options.entry_points' if new_section_name in setupcfg: return struct, opts new_section = ConfigUpdater() new_section.read_string(section_str) new_section = new_section[new_section_name] add_after_sect = 'options.extras_require' if add_after_sect not in setupcfg: # user removed it for some reason, default to metadata add_after_sect = 'metadata' setupcfg[add_after_sect].add_after.section(new_section).space() if not opts['pretend']: setupcfg.update_file() return struct, opts
[docs]def add_setup_requires(struct, opts): """Add `setup_requires` in setup.cfg Args: struct (dict): previous directory structure (ignored) opts (dict): options of the project Returns: tuple(dict, dict): structure as dictionary of dictionaries and input options """ setupcfg = read_setupcfg(opts['project']) comment = ("# DON'T CHANGE THE FOLLOWING LINE! " "IT WILL BE UPDATED BY PYSCAFFOLD!") options = setupcfg['options'] if 'setup_requires' in options: return struct, opts version_str = get_setup_requires_version() (options['package_dir'].add_after .comment(comment) .option('setup_requires', version_str)) if not opts['pretend']: setupcfg.update_file() return struct, opts
[docs]def update_pyscaffold_version(project_path, pretend): """Update `setup_requires` in setup.cfg Args: project_path (str): path to project pretend (bool): only pretend to do something """ setupcfg = read_setupcfg(project_path) setupcfg['options']['setup_requires'] = get_setup_requires_version() setupcfg['pyscaffold']['version'] = pyscaffold_version if not pretend: setupcfg.update_file()