Skip to content
This repository was archived by the owner on Mar 13, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
]
description = "One line description of your module"
dependencies = [] # Add project dependencies here, e.g. ["click", "numpy"]
dependencies = ["tomli"] # Add project dependencies here, e.g. ["click", "numpy"]
dynamic = ["version"]
license.file = "LICENSE"
readme = "README.rst"
Expand Down
143 changes: 128 additions & 15 deletions src/python3_pip_skeleton/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from tempfile import TemporaryDirectory
from typing import List

import tomli

from . import __version__

__all__ = ["main"]
Expand Down Expand Up @@ -144,6 +146,26 @@ def verify_not_adopted(root: Path):
)


def obtain_git_author_email(path: Path, force_local=True):
# If we force local then we require there to be a local .git we can look for
# the username and password on.
# If we don't force local then we will try to look for a local .git, if not found
# git will use the global user.[name, email].
if force_local and not (path / ".git").exists():
raise FileNotFoundError(
".git could not be found when searching "
f"for a username and password in {path}"
)
author = str(
git("--git-dir", path / ".git", "config", "--get", "user.name").strip()
)
author_email = str(
git("--git-dir", path / ".git", "config", "--get", "user.email").strip()
)

return author, author_email


def new(args):
path: Path = args.path

Expand All @@ -156,23 +178,109 @@ def new(args):
else:
path.mkdir(parents=True)

if args.full_name and args.email:
author, author_email = args.full_name, args.email
else:
author, author_email = obtain_git_author_email(Path("."), force_local=False)

git("init", "-b", "main", cwd=path)
print(f"Created git repo in {path}")
merge_skeleton(
path=path,
org=args.org,
full_name=args.full_name or git("config", "--get", "user.name").strip(),
email=args.email or git("config", "--get", "user.email").strip(),
full_name=author,
email=author_email,
from_branch=args.from_branch or "main",
package=package,
)


cfg_issue = """Missing parameter in setup.cfg. Expected format:
[metadata]
name = example
author = Firstname Lastname
author_email = email@address.com"""
[metadata]
name = example
author = Firstname Lastname
author_email = email@address.com

------- pyproject.toml
[[project.authors]]
name = "Firstname Lastname"
email = "email@address.com"
"""


def obtain_author_name_email(path: Path) -> tuple:
Comment thread
evvaaaa marked this conversation as resolved.
author: str = ""
author_email: str = ""
file_path_setup_cfg: Path = path / "setup.cfg"
file_path_pyproject_toml: Path = path / "pyproject.toml"

# Parse for an author name, email. The order of preference used is
# setup.cfg -> pyproject.toml -> .git -> user input.
# Author and Email are recieved together to avoid mismatches from
# obtaining in different places.

if file_path_setup_cfg.exists():
try:
conf_cfg = ConfigParser()
conf_cfg.read(file_path_setup_cfg)

if "metadata" in conf_cfg:
if "author" in conf_cfg["metadata"]:
author = conf_cfg["metadata"]["author"]
if "author_email" in conf_cfg["metadata"]:
author_email = conf_cfg["metadata"]["author_email"]
except Exception as exception:
print(
"\033[1mUnable to parse setup.cfg because of the following error, "
"will try other sources:\033[0m"
)
print(exception)
print()

if (not author or not author_email) and file_path_pyproject_toml.exists():
file = open(file_path_pyproject_toml, "rb")
try:
conf_toml = tomli.load(file)
if "project" in conf_toml and "authors" in conf_toml["project"]:
# pyproject.toml will use "author" or "name" so we look for both
for author_variable_name in ["author", "name"]:
if author_variable_name in conf_toml["project"]["authors"][0]:
author = conf_toml["project"]["authors"][0][
author_variable_name
]
if "email" in conf_toml["project"]["authors"][0]:
author_email = conf_toml["project"]["authors"][0]["email"]
except Exception as exception:
# We want to use something else if the pyproject.toml has some errors.
print(
"\033[1mUnable to parse project.toml because of the following error, "
"will try other sources:\033[0m"
)
print(exception)
print()
file.close()

if not author or not author_email:
try:
author, author_email = obtain_git_author_email(path)
except FileNotFoundError:
print(
"\033[1mUnable to find a .git in the repo,"
"will try other sources\033[0m"
)

# If all else fails, just ask the user.
if not author or not author_email:
print(cfg_issue)
print("Enter author name manually:")
author = str(input())
print("Enter author email manually:")
author_email = str(input())

assert author, "Inputted no author"
assert author_email, "Inputted no author_email"

return author, author_email


def existing(args):
Expand All @@ -181,21 +289,20 @@ def existing(args):

assert path.is_dir(), f"Expected {path} to be an existing directory"
package = validate_package(args)
file_path: Path = path / "setup.cfg"
assert file_path.is_file(), "Expected a setup.cfg file in the directory."

if not args.force:
verify_not_adopted(args.path)

conf = ConfigParser()
conf.read(path / "setup.cfg")
assert "metadata" in conf, cfg_issue
assert "author" in conf["metadata"], cfg_issue
assert "author_email" in conf["metadata"], cfg_issue
if args.full_name and args.email:
author, author_email = args.full_name, args.email
else:
author, author_email = obtain_author_name_email(path)

merge_skeleton(
path=args.path,
org=args.org,
full_name=conf["metadata"]["author"],
email=conf["metadata"]["author_email"],
full_name=author,
email=author_email,
from_branch=args.from_branch or "main",
package=package,
)
Expand Down Expand Up @@ -248,6 +355,12 @@ def main(args=None):
sub.add_argument(
"--package", default=None, help="Package name, defaults to directory name"
)
sub.add_argument(
"--full-name", default=None, help="Full name, defaults to git config user.name"
)
sub.add_argument(
"--email", default=None, help="Email address, defaults to git config user.email"
)
sub.add_argument(
"--from-branch",
default=None,
Expand Down
115 changes: 110 additions & 5 deletions tests/test_adopt.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import subprocess
import sys
from os import makedirs
from os import chdir, makedirs
from pathlib import Path
from unittest.mock import patch

import pytest
import toml
Expand All @@ -23,7 +24,33 @@ def test_cli_version():
assert output.strip() == __version__


def test_new_module(tmp_path: Path):
@pytest.mark.parametrize(
"extra_args", [(), ("--full-name=Firstname Lastname", "--email=me@myaddress.com")]
)
def test_new_module(extra_args, tmp_path: Path):
if not extra_args:
original_path = Path(".").absolute()
check_output("git", "init", str(tmp_path), cwd=tmp_path)
check_output(
"git",
"--git-dir",
str(tmp_path / ".git"),
"config",
"user.name",
"Firstname Lastname",
cwd=tmp_path,
)
check_output(
"git",
"--git-dir",
str(tmp_path / ".git"),
"config",
"user.email",
"me@myaddress.com",
cwd=tmp_path,
)
chdir(tmp_path)

module = tmp_path / "my-module"
output = check_output(
sys.executable,
Expand All @@ -32,10 +59,13 @@ def test_new_module(tmp_path: Path):
"new",
"--org=myorg",
"--package=my_module",
"--full-name=Firstname Lastname",
"--email=me@myaddress.com",
*extra_args,
str(module),
)

if not extra_args:
chdir(original_path)

assert output.strip().endswith(
"Developer instructions in docs/developer/tutorials/dev-install.rst"
)
Expand Down Expand Up @@ -72,6 +102,7 @@ def test_new_module(tmp_path: Path):


def test_new_module_existing_dir(tmp_path: Path):
print(Path(".").absolute())
module = tmp_path / "my-module"
makedirs(module / "existing_dir")

Expand Down Expand Up @@ -129,8 +160,19 @@ def test_new_module_merge_from_invalid_branch(tmp_path: Path):
assert "couldn't find remote ref fail" in str(excinfo.value)


def test_existing_module(tmp_path: Path):
SETUP_CFG = """[metadata]
name = example
author = Firstname Lastname
author_email = email@address.com
"""


@pytest.mark.parametrize(
"extra_args", [(), ("--full-name=Firstname Lastname", "--email=me@myaddress.com")]
)
def test_existing_module(extra_args, tmp_path: Path):
module = tmp_path / "scanspec"

__main__.git(
"clone",
"--depth",
Expand All @@ -140,12 +182,17 @@ def test_existing_module(tmp_path: Path):
"https://github.com/dls-controls/scanspec",
str(module),
)

with open(module / "setup.cfg", "w+") as setup_cfg:
setup_cfg.write(SETUP_CFG)

output = check_output(
sys.executable,
"-m",
"python3_pip_skeleton",
"existing",
"--org=epics-containers",
*extra_args,
str(module),
)
assert output.endswith(
Expand Down Expand Up @@ -233,3 +280,61 @@ def test_existing_module_merge_from_invalid_branch(tmp_path: Path):
str(module),
)
assert "couldn't find remote ref fail" in str(excinfo.value)


def test_obtain_git_author_email(tmp_path):
__main__.git("--git-dir", tmp_path / ".git", "init")
__main__.git("--git-dir", tmp_path / ".git", "config", "user.name", "Foo Bar")
__main__.git("--git-dir", tmp_path / ".git", "config", "user.email", "Foo@Bar")
assert __main__.obtain_git_author_email(tmp_path) == ("Foo Bar", "Foo@Bar")


def test_obtain_author_name_email_setup_cfg(tmp_path):
cfg_str = """
[metadata]
author = Foo Bar
author_email = Foo@Bar
"""
with open(tmp_path / "setup.cfg", "w+") as cfg_file:
cfg_file.write(cfg_str)
assert __main__.obtain_author_name_email(tmp_path) == ("Foo Bar", "Foo@Bar")


def test_obtain_author_name_email_pyproject_toml(tmp_path):
toml_str = """
[[project.authors]]
email = "Foo@Bar"
name = "Foo Bar"
"""
with open(tmp_path / "pyproject.toml", "w+") as toml_file:
toml_file.write(toml_str)
assert __main__.obtain_author_name_email(tmp_path) == ("Foo Bar", "Foo@Bar")


@patch("python3_pip_skeleton.__main__.input", return_value="Foo")
def test_obtain_author_name_email_botched_cfg_toml(input, tmp_path):
toml_str = """
email
name = "Foo Bar"
"""
cfg_str = """
author = Foo Bar
author_email = Foo@Bar
"""
with open(tmp_path / "setup.cfg", "w+") as cfg_file:
cfg_file.write(cfg_str)
with open(tmp_path / "pyproject.toml", "w+") as toml_file:
toml_file.write(toml_str)
assert __main__.obtain_author_name_email(tmp_path) == ("Foo", "Foo")


def test_obtain_author_name_email_git(tmp_path):
__main__.git("--git-dir", tmp_path / ".git", "init")
__main__.git("--git-dir", tmp_path / ".git", "config", "user.name", "Foo Bar")
__main__.git("--git-dir", tmp_path / ".git", "config", "user.email", "Foo@Bar")
assert __main__.obtain_author_name_email(tmp_path) == ("Foo Bar", "Foo@Bar")


@patch("python3_pip_skeleton.__main__.input", return_value="Foo")
def test_obtain_author_name_email_terminal_output(input, tmp_path):
assert __main__.obtain_author_name_email(tmp_path) == ("Foo", "Foo")