def test_raises_AttributeError_if_default_not_given(ctx, query): ctx.values = { 'test1': { 'sub': 'test value', } } with pytest.raises(AttributeError): context.get(query)
def clean(exclude: List[str]): """ Remove all unnecessary files. Args: exclude (list[str]): A list of path patterns to exclude from deletion. """ pretend = context.get('pretend', False) exclude = list(exclude) + conf.get('clean.exclude', []) clean_patterns = conf.get('clean.patterns', [ '*__pycache__*', '*.py[cod]', '*.swp', "*.mypy_cache", "*.pytest_cache", "*.build", ]) if context.get('verbose'): log.info('Clean patterns:') for pattern in clean_patterns: log.info(f' <90>{pattern}') log.info('Exclude:') for pattern in exclude: log.info(f' <90>{pattern}') num_files = 0 with util.timed_block() as t: files = fs.filtered_walk(conf.proj_path(), clean_patterns, exclude) log.info('') log.info('Deleting:') for path in files: try: num_files += 1 if not isdir(path): log.info(' <91>[file] <90>{}', path) if not pretend: os.remove(path) else: log.info(' <91>[dir] <90>{}', path) if not pretend: rmtree(path) except OSError: log.info("<33>Failed to remove <90>{}", path) if pretend: msg = "Would delete <33>{}<32> files. Took <33>{}<32>s" else: msg = "Deleted <33>{}<32> files in <33>{}<32>s" log.info(msg.format(num_files, t.elapsed_s))
def finish(fast_forward: bool): """ Merge current feature branch into develop. """ pretend = context.get('pretend', False) if not pretend and (git.staged() or git.unstaged()): log.err("You have uncommitted changes in your repo!\n" "You need to stash them before you merge the feature branch") sys.exit(1) develop = conf.get('git.devel_branch', 'develop') branch = git.current_branch(refresh=True) common.assert_branch_type('feature') hooks.register.call('pre-feature-finish', branch) # Merge feature into develop common.git_checkout(develop) common.git_pull(develop) common.git_merge(develop, branch.name, no_ff=not fast_forward) # Cleanup common.git_branch_delete(branch.name) common.git_prune() common.git_checkout(develop) hooks.register.call('post-feature-finish', branch)
def deploy(self, promote=False, quiet=False): # type: (bool, bool, bool) -> None """ Deploy the code to AppEngine. Args: promote (bool): Migrate the traffic to the deployed version. quiet (bool): Pass ``--quiet`` flag to gcloud command """ args = [ '--promote' if promote else '--no-promote', '--version {}'.format(self.app_version), '--project {}'.format(self.app_id), ] if quiet: args += ['--quiet'] cmd = 'gcloud app deploy {args} {deployables}'.format( deployables=fs.wrap_paths(self.deployables), args=' '.join(args)) if context.get('pretend', False): log.info("Would deploy version <35>{ver}<32> to <35>{app}".format( ver=self.app_version, app=self.app_id)) shell.cprint('<90>{}', cmd) else: log.info("Deploying version <35>{ver}<32> to <35>{app}".format( ver=self.app_version, app=self.app_id, )) shell.run(cmd)
def _manage_cmd(cmd, settings=None): # type: () -> None """ Run django ./manage.py command manually. This function eliminates the need for having ``manage.py`` (reduces file clutter). """ import sys from os import environ from peltak.core import conf from peltak.core import context from peltak.core import log sys.path.insert(0, conf.get('src_dir')) settings = settings or conf.get('django.settings', None) environ.setdefault("DJANGO_SETTINGS_MODULE", settings) args = sys.argv[0:-1] + cmd if context.get('pretend', False): log.info("Would run the following manage command:\n<90>{}", args) else: from django.core.management import execute_from_command_line execute_from_command_line(args)
def test_return_default_when_provided_and_value_not_found(ctx, query): ctx.values = { 'test1': { 'sub': 'test value', } } assert context.get(query, -1) == -1
def init(quick: bool, blank: bool, force: bool): """ Create an empty pelconf.yaml from template """ config_file = 'pelconf.yaml' prompt = "-- <35>{} <32>already exists. Wipe it?<0>".format(config_file) if not force and exists(config_file) and not click.confirm( shell.fmt(prompt)): log.info("Canceled") return ctx = dict(blank=blank) if not blank: form = InitForm().run(quick=quick) ctx.update(form.values) config_content = templates.Engine().render_file('pelconf.yaml', ctx) log.info('Writing <35>{}'.format(config_file)) fs.write_file(config_file, config_content) if context.get('verbose') > 0: print( f"{'- ' * 40}\n{shell.highlight(config_content, 'yaml')}{'- ' * 40}" )
def git_merge(base: str, head: str, no_ff: bool = False): """ Merge *head* into *base*. Args: base (str): The base branch. *head* will be merged into this branch. head (str): The branch that will be merged into *base*. no_ff (bool): If set to **True** it will force git to create merge commit. If set to **False** (default) it will do a fast-forward merge if possible. """ pretend = context.get('pretend', False) branch = git.current_branch(refresh=True) if branch.name != base and not pretend: git_checkout(base) args = [] if no_ff: args.append('--no-ff') log.info("Merging <33>{}<32> into <33>{}<32>", head, base) shell.run('git merge {args} {branch}'.format( args=' '.join(args), branch=head, )) if branch.name != base and not pretend: git_checkout(branch.name)
def finish(fast_forward: bool): """ Merge current feature branch into develop. """ pretend = context.get('pretend', False) if not pretend and (git.staged() or git.unstaged()): log.err("You have uncommitted changes in your repo!\n" "You need to stash them before you merge the hotfix branch") sys.exit(1) branch = git.current_branch(refresh=True) base = common.get_base_branch() prompt = "<32>Merge <33>{}<32> into <33>{}<0>?".format(branch.name, base) if not click.confirm(shell.fmt(prompt)): log.info("Cancelled") return common.assert_branch_type('task') hooks.register.call('pre-task-finish', branch, base) # Merge task into it's base feature branch common.git_checkout(base) common.git_pull(base) common.git_merge(base, branch.name, no_ff=not fast_forward) # Cleanup common.git_branch_delete(branch.name) common.git_prune() common.git_checkout(base) hooks.register.call('post-task-finish', branch, base)
def test_can_just_get_the_full_nested_object(ctx): ctx.values = { 'test1': { 'sub': 'test value', } } assert context.get('test1') == {'sub': 'test value'}
def test_get_existing_nested_value_works(ctx): ctx.values = { 'test1': { 'sub': 'test value', } } assert context.get('test1.sub') == 'test value'
def add_hooks(pre_commit: str, pre_push: str): """ Add git hooks for commit and push to run linting and tests. """ # Detect virtualenv the hooks should use # Detect virtualenv virtual_env = conf.get_env('VIRTUAL_ENV') if virtual_env is None: log.err("You are not inside a virtualenv") confirm_msg = ( "Are you sure you want to use global python installation " "to run your git hooks? [y/N] " ) click.prompt(confirm_msg, default='') if not click.confirm(confirm_msg): log.info("Cancelling") return load_venv = '' else: load_venv = 'source "{}/bin/activate"'.format(virtual_env) commit_hook = conf.proj_path('.git/hooks/pre-commit') push_hook = conf.proj_path('.git/hooks/pre-push') # Write pre-commit hook log.info("Adding pre-commit hook <33>{}", commit_hook) fs.write_file(commit_hook, util.remove_indent(''' #!/bin/bash PATH="/opt/local/libexec/gnubin:$PATH" {load_venv} {command} '''.format(load_venv=load_venv, command=pre_commit))) # Write pre-push hook log.info("Adding pre-push hook: <33>{}", push_hook) fs.write_file(push_hook, util.remove_indent(''' #!/bin/bash PATH="/opt/local/libexec/gnubin:$PATH" {load_venv} peltak test --allow-empty {command} '''.format(load_venv=load_venv, command=pre_push))) log.info("Making hooks executable") if not context.get('pretend', False): os.chmod(conf.proj_path('.git/hooks/pre-commit'), 0o755) os.chmod(conf.proj_path('.git/hooks/pre-push'), 0o755)
def assert_on_branch(branch_name: str): """ Print error and exit if *branch_name* is not the current branch. Args: branch_name (str): The supposed name of the current branch. """ branch = git.current_branch(refresh=True) if branch.name != branch_name: if context.get('pretend', False): log.info("Would assert that you're on a <33>{}<32> branch", branch_name) else: log.err("You're not on a <33>{}<31> branch!", branch_name) sys.exit(1)
def extract_from_files(files: List[str]) -> List[Todo]: todos: List[Todo] = [] for path in files: file_todos = _process_file(path) todos += file_todos if context.get('verbose') >= 1: if len(file_todos) > 0: shell.cprint( " <33>{:2} <32>TODOs in <90>{}".format(len(file_todos), path) ) else: shell.cprint(" <32>No TODOs in <90>{}".format(path)) return todos
def _render_todos(todos: List[Todo]) -> None: print('\n') for file_path, file_todos in itertools.groupby(todos, key=lambda x: x.file): shell.cprint(f"<92>{file_path}\n") for todo in sorted(file_todos, key=lambda x: x.lines.start): if context.get('verbose') >= 1: shell.cprint( f"<36>{todo.pretty_timestamp} <33>{todo.author}<0>\n" f"<95>{todo.file}:{todo.lines} <90>{todo.sha1}<0>\n\n" f"{textwrap.indent(todo.color_text, ' ')}\n\n") else: shell.cprint( f" <95>:{todo.lines} <36>{todo.pretty_timestamp} " f"<33>{todo.author_email} <90>{todo.sha1}<0><0>\n\n" f"{textwrap.indent(todo.color_text, ' ')}\n") print() log.info(f"Found <33>{len(todos)}<32> TODOs")
def assert_branch_type(branch_type: str): """ Print error and exit if the current branch is not of a given type. Args: branch_type (str): The branch type. This assumes the branch is in the '<type>/<title>` format. """ branch = git.current_branch(refresh=True) if branch.type != branch_type: if context.get('pretend', False): log.info("Would assert that you're on a <33>{}/*<32> branch", branch_type) else: log.err("Not on a <33>{}<31> branch!", branch_type) fmt = ("The branch must follow <33>{required_type}/<name><31>" "format and your branch is called <33>{name}<31>.") log.err(fmt, required_type=branch_type, name=branch.name) sys.exit(1)
def write_file(path: str, content: Union[str, bytes], mode: str = 'w') -> None: """ --pretend aware file writing. You can always write files manually but you should always handle the --pretend case. Args: path (str): content (str): mode (str): """ from peltak.core import context from peltak.core import log if context.get('pretend', False): log.info("Would overwrite <34>{path}<32> with:\n<90>{content}", path=path, content=content) else: with open(path, mode) as fp: fp.write(content)
def finish(fast_forward: bool): """ Merge current release into develop and master and tag it. """ pretend = context.get('pretend', False) if not pretend and (git.staged() or git.unstaged()): log.err("You have uncommitted changes in your repo!\n" "You need to stash them before you merge the release branch") sys.exit(1) develop = conf.get('git.devel_branch', 'develop') master = conf.get('git.master_branch', 'master') branch = git.current_branch(refresh=True) common.assert_branch_type('release') hooks.register.call('pre-release-finish', branch) # Merge release into master common.git_checkout(develop) common.git_pull(develop) common.git_merge(develop, branch.name, no_ff=not fast_forward) # Merge release into develop common.git_checkout(master) common.git_pull(master) common.git_merge(master, branch.name, no_ff=not fast_forward) # Tag the release commit with version number tag(changelog()) # Cleanup common.git_branch_delete(branch.name) common.git_prune() common.git_checkout(master) hooks.register.call('post-release-finish', branch)
def test_get_existing_root_value_works(ctx): ctx.values = { 'test1': 'test value', } assert context.get('test1') == 'test value'