Skip to content

Footing Package

footing.setup

Creates and initializes a project from a template.

footing.setup.setup

setup(template: str, version: str | None = None) -> None

Sets up a new project from a template

Note that the footing.constants.FOOTING_ENV_VAR is set to 'setup' during the duration of this function.

Parameters:

Name Type Description Default
template str

The git path to a template

required
version str | None

The version of the template to use when updating. Defaults to the latest version

None
Source code in footing/setup.py
@footing.utils.set_cmd_env_var("setup")
def setup(template: str, version: str | None = None) -> None:
    """Sets up a new project from a template

    Note that the `footing.constants.FOOTING_ENV_VAR` is set to 'setup' during the duration
    of this function.

    Args:
        template: The git path to a template
        version: The version of the template to use when updating. Defaults
            to the latest version
    """
    footing.check.not_in_git_repo()

    repo_path = footing.utils.get_repo_path(template)
    msg = (
        "You will be prompted for the parameters of your new project."
        " Please read the docs at https://github.com/{} before entering parameters."
    ).format(repo_path)
    print(msg)

    cc_repo_dir, config = footing.utils.get_cookiecutter_config(template, version=version)

    if not version:
        with footing.utils.cd(cc_repo_dir):
            ret = footing.utils.shell("git rev-parse HEAD", stdout=subprocess.PIPE)
            version = ret.stdout.decode("utf-8").strip()

    _generate_files(repo_dir=cc_repo_dir, config=config, template=template, version=version)

footing.update

Updates a footing project with the latest template.

footing.update.up_to_date

up_to_date(version: str | None = None) -> bool

Checks if a footing project is up to date with the repo

Note that the footing.constants.FOOTING_ENV_VAR is set to 'update' for the duration of this function.

Parameters:

Name Type Description Default
version str | None

Update against this git SHA or branch of the template

None

Returns:

Type Description
bool

True if up to date with version (or latest version), False otherwise

Raises:

Type Description
`NotInGitRepoError`

When running outside of a git repo

`InvalidFootingProjectError`

When not inside a valid footing repository

Source code in footing/update.py
@footing.utils.set_cmd_env_var("update")
def up_to_date(version: str | None = None) -> bool:
    """Checks if a footing project is up to date with the repo

    Note that the `footing.constants.FOOTING_ENV_VAR` is set to 'update' for the duration of this
    function.

    Args:
        version: Update against this git SHA or branch of the template

    Returns:
        True if up to date with ``version`` (or latest version), False otherwise

    Raises:
        `NotInGitRepoError`: When running outside of a git repo
        `InvalidFootingProjectError`: When not inside a valid footing repository
    """
    footing.check.in_git_repo()
    footing.check.is_footing_project()

    footing_config = footing.utils.read_footing_config()
    old_template_version = footing_config["_version"]
    new_template_version = version or _get_latest_template_version(footing_config["_template"])

    return new_template_version == old_template_version

footing.update.update

update(
    old_template: str | None = None,
    old_version: str | None = None,
    new_template: str | None = None,
    new_version: str | None = None,
    enter_parameters: bool = False,
) -> bool

Updates the footing project to the latest template

Proceeeds in the following steps:

  1. Ensure we are inside the project repository
  2. Obtain the latest version of the package template
  3. If the package is up to date with the latest template, return
  4. If not, create an empty template branch with a new copy of the old template
  5. Create an update branch from HEAD and merge in the new template copy
  6. Create a new copy of the new template and merge into the empty template branch
  7. Merge the updated empty template branch into the update branch
  8. Ensure footing.yaml reflects what is in the template branch
  9. Remove the empty template branch

Note that the footing.constants.FOOTING_ENV_VAR is set to 'update' for the duration of this function.

Two branches will be created during the update process, one named _footing_update and one named _footing_update_temp. At the end of the process, _footing_update_temp will be removed automatically. The work will be left in _footing_update in an uncommitted state for review. The update will fail early if either of these branches exist before the process starts.

Parameters:

Name Type Description Default
old_template str | None

The old template from which to update. Defaults to the template in footing.yaml.

None
old_version str | None

The old version of the template. Defaults to the version in footing.yaml.

None
new_template str | None

The new template for updating. Defaults to the template in footing.yaml

None
new_version str | None

The new version of the new template to update. Defaults to the latest version of the new template.

None
enter_parameters bool

Force entering template parameters for the project

False

Raises:

Type Description
`NotInGitRepoError`

When not inside of a git repository

`InvalidFootingProjectError`

When not inside a valid footing repository

`InDirtyRepoError`

When an update is triggered while the repo is in a dirty state

`ExistingBranchError`

When an update is triggered and there is an existing update branch

Returns:

Type Description
bool

True if update was performed or False if template was already up to date

Source code in footing/update.py
@footing.utils.set_cmd_env_var("update")
def update(
    old_template: str | None = None,
    old_version: str | None = None,
    new_template: str | None = None,
    new_version: str | None = None,
    enter_parameters: bool = False,
) -> bool:
    """Updates the footing project to the latest template

    Proceeeds in the following steps:

    1. Ensure we are inside the project repository
    2. Obtain the latest version of the package template
    3. If the package is up to date with the latest template, return
    4. If not, create an empty template branch with a new copy of the old template
    5. Create an update branch from HEAD and merge in the new template copy
    6. Create a new copy of the new template and merge into the empty template branch
    7. Merge the updated empty template branch into the update branch
    8. Ensure footing.yaml reflects what is in the template branch
    9. Remove the empty template branch

    Note that the `footing.constants.FOOTING_ENV_VAR` is set to 'update' for the
    duration of this function.

    Two branches will be created during the update process, one named
    ``_footing_update`` and one named ``_footing_update_temp``. At the end of
    the process, ``_footing_update_temp`` will be removed automatically. The
    work will be left in ``_footing_update`` in an uncommitted state for
    review. The update will fail early if either of these branches exist
    before the process starts.

    Args:
        old_template: The old template from which to update. Defaults to the template in
            footing.yaml.
        old_version: The old version of the template. Defaults to the version in footing.yaml.
        new_template: The new template for updating. Defaults to the template in footing.yaml
        new_version: The new version of the new template to update. Defaults to the latest version
            of the new template.
        enter_parameters: Force entering template parameters for the project

    Raises:
        `NotInGitRepoError`: When not inside of a git repository
        `InvalidFootingProjectError`: When not inside a valid footing repository
        `InDirtyRepoError`: When an update is triggered while the repo is in a dirty state
        `ExistingBranchError`: When an update is triggered and there is an existing
            update branch

    Returns:
        True if update was performed or False if template was already up to date
    """
    update_branch = footing.constants.UPDATE_BRANCH_NAME
    temp_update_branch = footing.constants.TEMP_UPDATE_BRANCH_NAME

    footing.check.in_git_repo()
    footing.check.in_clean_repo()
    footing.check.is_footing_project()
    footing.check.not_has_branch(update_branch)
    footing.check.not_has_branch(temp_update_branch)

    footing_config = footing.utils.read_footing_config()
    old_template = old_template or footing_config["_template"]
    new_template = new_template or footing_config["_template"]
    old_version = old_version or footing_config["_version"]
    new_version = new_version or _get_latest_template_version(new_template)

    if new_template == old_template and new_version == old_version and not enter_parameters:
        print("No updates have happened to the template, so no files were updated")
        return False

    print("Creating branch {} for processing the update".format(update_branch))
    footing.utils.shell("git checkout -b {}".format(update_branch), stderr=subprocess.DEVNULL)

    print("Creating temporary working branch {}".format(temp_update_branch))
    footing.utils.shell(
        "git checkout --orphan {}".format(temp_update_branch),
        stderr=subprocess.DEVNULL,
    )
    footing.utils.shell("git rm -rf .", stdout=subprocess.DEVNULL)
    _apply_template(old_template, ".", checkout=old_version, extra_context=footing_config)
    footing.utils.shell("git add .")
    footing.utils.shell(
        'git commit --no-verify -m "Initialize template from version {}"'.format(old_version),
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )

    print("Merge old template history into update branch.")
    footing.utils.shell("git checkout {}".format(update_branch), stderr=subprocess.DEVNULL)
    footing.utils.shell(
        "git merge -s ours --no-edit --allow-unrelated-histories {}".format(temp_update_branch),
        stderr=subprocess.DEVNULL,
    )

    print("Update template in temporary branch.")
    footing.utils.shell("git checkout {}".format(temp_update_branch), stderr=subprocess.DEVNULL)
    footing.utils.shell("git rm -rf .", stdout=subprocess.DEVNULL)

    # If the cookiecutter.json files have changed or the templates have changed,
    # the user will need to re-enter the cookiecutter config
    needs_new_cc_config = _needs_new_cc_config_for_update(
        old_template, old_version, new_template, new_version
    )
    if needs_new_cc_config:
        if old_template != new_template:
            cc_config_input_msg = (
                "You will be prompted for the parameters of the new template."
                " Please read the docs at https://github.com/{} before entering parameters."
                " Press enter to continue"
            ).format(footing.utils.get_repo_path(new_template))
        else:
            cc_config_input_msg = (
                "A new template variable has been defined in the updated template."
                " You will be prompted to enter all of the variables again. Variables"
                " already configured in your project will have their values set as"
                " defaults. Press enter to continue"
            )

        input(cc_config_input_msg)

    # Even if there is no detected need to re-enter the cookiecutter config, the user
    # can still re-enter config parameters with the "enter_parameters" flag
    if needs_new_cc_config or enter_parameters:
        _, footing_config = footing.utils.get_cookiecutter_config(
            new_template, default_config=footing_config, version=new_version
        )

    _apply_template(new_template, ".", checkout=new_version, extra_context=footing_config)
    footing.utils.write_footing_config(footing_config, new_template, new_version)

    footing.utils.shell("git add .")
    footing.utils.shell(
        'git commit --no-verify -m "Update template to version {}"'.format(new_version),
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )

    print("Merge updated template into update branch.")
    footing.utils.shell("git checkout {}".format(update_branch), stderr=subprocess.DEVNULL)
    footing.utils.shell(
        "git merge --no-commit {}".format(temp_update_branch),
        check=False,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    # The footing.yaml file should always reflect what is in the new template
    footing.utils.shell(
        "git checkout --theirs {}".format(footing.constants.FOOTING_CONFIG_FILE),
        check=False,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )

    print("Remove temporary template branch {}".format(temp_update_branch))
    footing.utils.shell(
        "git branch -D {}".format(temp_update_branch),
        stdout=subprocess.DEVNULL,
    )

    print(
        textwrap.dedent(
            """\
        Updating complete!

        Please review the changes with "git status" for any errors or
        conflicts. Once you are satisfied with the changes, add, commit,
        push, and open a PR with the branch {}
    """
        ).format(update_branch)
    )
    return True

footing.ls

Lists all footing templates and projects spun up with those templates.

footing.ls.ls

ls(forge: str, template: str | None = None) -> dict[str, str]

Lists all templates under a root path or list all projects spun up under a root path and a template path.

The root path must be either a Github organization/user (e.g. github.com/organization) or a Gitlab group (e.g. gitlab.com/my/group).

Note that the footing.constants.FOOTING_ENV_VAR is set to 'ls' for the duration of this function.

Parameters:

Name Type Description Default
forge str

A root git storage path. For example, a Github organization (github.com/Organization) or a gitlab group (gitlab.com/my/group).

required
template str | None

An optional template path. If provided, the returned values are projects under root created using the template.

None

Returns:

Type Description
dict[str, str]

xA dictionary of repository information keyed on the url.

Raises:

Type Description
`InvalidForgeError`

When forge is invalid

Source code in footing/ls.py
@footing.utils.set_cmd_env_var("ls")
def ls(forge: str, template: str | None = None) -> dict[str, str]:
    """Lists all templates under a root path or list all projects spun up under
    a root path and a template path.

    The ``root`` path must be either a Github organization/user (e.g. github.com/organization)
    or a Gitlab group (e.g. gitlab.com/my/group).

    Note that the `footing.constants.FOOTING_ENV_VAR` is set to 'ls' for the duration of this
    function.

    Args:
        forge: A root git storage path.  For example, a Github organization
            (github.com/Organization) or a gitlab group (gitlab.com/my/group).
        template: An optional template path. If provided, the
            returned values are projects under ``root`` created using the template.

    Returns:
        xA dictionary of repository information keyed on the url.

    Raises:
        `InvalidForgeError`: When ``forge`` is invalid
    """
    client = footing.forge.from_path(forge)
    return client.ls(forge, template)

footing.constants

Constants for footing.

footing.exceptions

Footing exceptions.

footing.exceptions.CheckRunError

Bases: Error

When running footing update --check errors

footing.exceptions.Error

Bases: Exception

The top-level error for footing

footing.exceptions.ExistingBranchError

Bases: Error

Thrown when a specifically named branch exists or doesn't exist as expected.

footing.exceptions.InDirtyRepoError

Bases: Error

Thrown when running in a dirty git repo

footing.exceptions.InGitRepoError

Bases: Error

Thrown when running inside of a git repository

footing.exceptions.InvalidCurrentBranchError

Bases: Error

Thrown when a command cannot run because of the current git branch

footing.exceptions.InvalidEnvironmentError

Bases: Error

Thrown when required environment variables are not set

footing.exceptions.InvalidFootingProjectError

Bases: Error

Thrown when the repository was not created with footing

footing.exceptions.InvalidForgeError

Bases: Error

An invalid forge was passed to ls.

footing.exceptions.InvalidGitlabGroupError

Bases: Error

Thrown when an invalid Gitlab group is provided

footing.exceptions.InvalidTemplatePathError

Bases: Error

Thrown when a template path is not a Github SSH path

footing.exceptions.NotInGitRepoError

Bases: Error

Thrown when not running inside of a git repo

footing.exceptions.NotUpToDateWithTemplateError

Bases: Error

Thrown when a footing project is not up to date with the template