Beispiel #1
0
def test_create_doesnt_add_packages_if_not_specified(mocker: MockerFixture,
                                                     silent: bool, stdout,
                                                     stderr):
    conda = Conda(root=Path("somewhere"),
                  environment_name="test",
                  conda="notconda")

    mock_subprocess = mocker.patch("pytoil.environments.conda.subprocess.run",
                                   autospec=True)

    # Mock the return of get_envs_dir so it thinks it exists regardless
    # of whether the tester has conda installed or not
    mocker.patch(
        "pytoil.environments.conda.Conda.get_envs_dir",
        autospec=True,
        return_value=Path("anaconda3/envs"),
    )

    conda.create(silent=silent)

    mock_subprocess.assert_called_once_with(
        ["notconda", "create", "-y", "--name", "test", "python=3"],
        cwd=conda.project_path,
        stdout=stdout,
        stderr=stderr,
    )
Beispiel #2
0
def test_install_self_raises_if_conda_not_installed(mocker: MockerFixture,
                                                    silent: bool):
    mocker.patch("pytoil.environments.conda.Conda.create_from_yml",
                 autospec=True)

    conda = Conda(root=Path("somewhere"), environment_name="testy", conda=None)

    with pytest.raises(CondaNotInstalledError):
        conda.install_self(silent=silent)
Beispiel #3
0
def test_get_envs_dir_raises_if_none_found(mocker: MockerFixture,
                                           fake_home_folder_no_conda: Path):

    mocker.patch(
        "pytoil.environments.conda.Path.home",
        autospec=True,
        return_value=fake_home_folder_no_conda,
    )

    with pytest.raises(UnsupportedCondaInstallationError):
        Conda.get_envs_dir()
Beispiel #4
0
def test_install_raises_if_environment_doesnt_exist(mocker: MockerFixture):
    mocker.patch("pytoil.environments.Conda.exists",
                 autospec=True,
                 return_value=False)

    conda = Conda(root=Path("somewhere"),
                  environment_name="testy",
                  conda="notconda")

    with pytest.raises(EnvironmentDoesNotExistError):
        conda.install(packages=["black", "mypy", "isort"])
Beispiel #5
0
def test_create_raises_if_environment_already_exists(mocker: MockerFixture):
    conda = Conda(root=Path("somewhere"),
                  environment_name="test",
                  conda="notconda")

    # Make it think our "test" environment already exists
    mocker.patch("pytoil.environments.conda.Conda.exists",
                 autospec=True,
                 return_value=True)

    with pytest.raises(EnvironmentAlreadyExistsError):
        conda.create()
Beispiel #6
0
def test_create_from_yml_raises_if_conda_not_installed(mocker: MockerFixture):
    conda = Conda(root=Path("somewhere"), environment_name="test")

    # Ensure shutil.which returns None
    mocker.patch(
        "pytoil.environments.conda.shutil.which",
        autospec=True,
        return_value=None,
    )

    with pytest.raises(CondaNotInstalledError):
        conda.create_from_yml(Path("somewhere"), conda="notconda")
Beispiel #7
0
def test_export_yml(mocker: MockerFixture, temp_environment_yml: Path):
    # It must think the environment exists
    mocker.patch(
        "pytoil.environments.conda.Conda.exists",
        autospec=True,
        return_value=True,
    )

    conda = Conda(root=temp_environment_yml.parent,
                  environment_name="testy",
                  conda="notconda")

    class Process(NamedTuple):
        content: str = """
        name: testy
        channels:
        - defaults
        - conda-forge
        dependencies:
        - python=3
        - invoke
        - black
        - flake8
        - isort
        - mypy
        - rich
        - numpy
        - requests
        - pandas
        """

        @property
        def stdout(self) -> str:
            return self.content

    mock_subprocess = mocker.patch(
        "pytoil.environments.conda.subprocess.run",
        autospec=True,
        return_value=Process(),
    )

    conda.export_yml()

    mock_subprocess.assert_called_once_with(
        ["notconda", "env", "export", "--from-history", "--name", "testy"],
        cwd=temp_environment_yml.parent.resolve(),
        capture_output=True,
        encoding="utf-8",
    )

    assert temp_environment_yml.read_text(
        encoding="utf-8") == Process().content
Beispiel #8
0
def test_export_yml_raises_on_missing_env(mocker: MockerFixture):

    mocker.patch(
        "pytoil.environments.conda.Conda.exists",
        autospec=True,
        return_value=False,
    )

    with pytest.raises(EnvironmentDoesNotExistError):
        env = Conda(root=Path("somewhere"),
                    environment_name="testy",
                    conda="notconda")
        env.export_yml()
Beispiel #9
0
def test_conda_repr():
    conda = Conda(root=Path("somewhere"),
                  environment_name="test",
                  conda="notconda")
    assert (repr(conda) ==
            f"Conda(root={Path('somewhere')!r}, environment_name='test',"
            " conda='notconda')")
Beispiel #10
0
def test_install_self_calls_create_from_yml(mocker: MockerFixture,
                                            silent: bool):

    mock_create_from_yml = mocker.patch(
        "pytoil.environments.conda.Conda.create_from_yml", autospec=True)

    conda = Conda(root=Path("somewhere"),
                  environment_name="testy",
                  conda="notconda")

    conda.install_self(silent=silent)

    mock_create_from_yml.assert_called_once_with(
        project_path=Path("somewhere").resolve(),
        silent=silent,
        conda="notconda")
Beispiel #11
0
def test_create_from_yml_raises_on_bad_yml_file(
        mocker: MockerFixture, bad_temp_environment_yml: Path):

    # Ensure shutil.which doesn't fail
    mocker.patch(
        "pytoil.environments.conda.shutil.which",
        autospec=True,
        return_value="notconda",
    )

    conda = Conda(root=Path("somewhere"),
                  environment_name="test",
                  conda="notconda")

    with pytest.raises(BadEnvironmentFileError):
        conda.create_from_yml(bad_temp_environment_yml.parent,
                              conda="notconda")
Beispiel #12
0
def test_exists(mocker: MockerFixture, exists_return: bool, want: bool):
    mocker.patch(
        "pytoil.environments.conda.Path.exists",
        autospec=True,
        return_value=exists_return,
    )

    # Mock the return of get_envs_dir so it thinks it exists regardless
    # of whether the tester has conda installed or not
    mocker.patch(
        "pytoil.environments.conda.Conda.get_envs_dir",
        autospec=True,
        return_value=Path("anaconda3/envs"),
    )

    conda = Conda(root=Path("somewhere"),
                  environment_name="test",
                  conda="notconda")

    assert conda.exists() is want
Beispiel #13
0
def test_create_from_yml_raises_if_environment_already_exists(
        mocker: MockerFixture, temp_environment_yml: Path):
    # Ensure shutil.which doesn't fail
    mocker.patch(
        "pytoil.environments.conda.shutil.which",
        autospec=True,
        return_value="notconda",
    )

    # Make it think the environment already exists
    mocker.patch("pytoil.environments.conda.Conda.exists",
                 autospec=True,
                 return_value=True)

    conda = Conda(root=Path("somewhere"),
                  environment_name="test",
                  conda="notconda")

    with pytest.raises(EnvironmentAlreadyExistsError):
        conda.create_from_yml(project_path=temp_environment_yml.parent,
                              conda="notconda")
Beispiel #14
0
def test_create_from_yml_correctly_calls_subprocess(mocker: MockerFixture,
                                                    temp_environment_yml: Path,
                                                    silent: bool, stdout,
                                                    stderr):

    # Mock out the actual call to conda
    mock_subprocess = mocker.patch("pytoil.environments.conda.subprocess.run",
                                   autospec=True)

    # Give it a fake envs dir
    mocker.patch(
        "pytoil.environments.conda.Conda.get_envs_dir",
        autospec=True,
        return_value=Path("/Users/testyconda3/envs"),
    )

    # Ensure shutil.which doesn't fail
    mocker.patch(
        "pytoil.environments.conda.shutil.which",
        autospec=True,
        return_value="conda",
    )

    conda = Conda(root=Path("somewhere"),
                  environment_name="test",
                  conda="notconda")

    conda.create_from_yml(project_path=temp_environment_yml.parent,
                          conda="notconda",
                          silent=silent)

    mock_subprocess.assert_called_once_with(
        [
            "notconda", "env", "create", "--file",
            f"{temp_environment_yml.resolve()}"
        ],
        cwd=temp_environment_yml.resolve().parent,
        stdout=stdout,
        stderr=stderr,
    )
Beispiel #15
0
def test_get_envs_dir_returns_correctly_for_mambaforge(
        mocker: MockerFixture, fake_home_folder_mambaforge: Path):

    mocker.patch(
        "pytoil.environments.conda.Path.home",
        autospec=True,
        return_value=fake_home_folder_mambaforge,
    )

    env = Conda.get_envs_dir()

    expected_env_dir = fake_home_folder_mambaforge.joinpath("mambaforge/envs")

    assert env == expected_env_dir
Beispiel #16
0
def test_install_passes_correct_command(mocker: MockerFixture, name: str,
                                        packages: list[str], silent: bool,
                                        stdout, stderr):

    fake_project = Path("/Users/me/projects/fakeproject")

    mocker.patch("pytoil.environments.Conda.exists",
                 autospec=True,
                 return_value=True)

    mock_subprocess = mocker.patch("pytoil.environments.conda.subprocess.run",
                                   autospec=True)

    env = Conda(root=fake_project, environment_name="testy", conda="notconda")

    env.install(packages=packages, silent=silent)

    mock_subprocess.assert_called_once_with(
        ["notconda", "install", "-y", "--name", "testy", *packages],
        cwd=fake_project.resolve(),
        stdout=stdout,
        stderr=stderr,
    )
Beispiel #17
0
def test_conda_default(mocker: MockerFixture):
    conda = Conda(root=Path("somewhere"), environment_name="test")

    # Mock the return of get_envs_dir so it thinks it exists regardless
    # of whether the tester has conda installed or not
    mocker.patch(
        "pytoil.environments.conda.Conda.get_envs_dir",
        autospec=True,
        return_value=Path("anaconda3/envs"),
    )

    assert conda.project_path == Path("somewhere").resolve()
    assert conda.executable == Path("anaconda3/envs").joinpath(
        "test/bin/python")
    assert conda.name == "conda"
    assert conda.environment_name == "test"
    assert conda.conda == shutil.which("conda")
Beispiel #18
0
    def dispatch_env(self, config: Config) -> Environment | None:
        """
        Returns the correct environment object for the calling `Repo`,
        or `None` if it cannot detect the environment.

        Therefore all usage should first check for `None`.

        Returns:
            Optional[Environment]: The correct environment object if it was
                able to detect, or `None`.
        """
        # This is where the magic happens for automatic environment detection
        # and installation

        # Each of the environment objects below implements the `Environment` Protocol
        # and has an `install_self` method that does the correct thing for it's environment

        exists = (
            self.is_conda(),
            self.is_requirements(),
            self.is_setuptools(),
            self.is_poetry(),
            self.is_flit(),
        )

        conda, requirements, setuptools, poetry, flit = exists

        if conda:
            return Conda(
                root=self.local_path, environment_name=self.name, conda=config.conda_bin
            )
        elif requirements:
            return Requirements(root=self.local_path)
        elif setuptools:
            return Venv(root=self.local_path)
        elif poetry:
            return Poetry(root=self.local_path)
        elif flit:
            return Flit(root=self.local_path)
        else:
            # Could not autodetect, this is handled by the CLI
            return None
Beispiel #19
0
def new(  # noqa: C901
    config: Config,
    project: str,
    packages: tuple[str, ...],
    cookie: str | None,
    _copier: str | None,
    starter: str | None,
    venv: str | None,
    no_git: bool = False,
) -> None:
    """
    Create a new development project.

    Bare usage will simply create an empty folder in your configured projects
    directory.

    You can also create a project from a cookiecutter or copier template by passing a valid
    url to the '--cookie/-c' or '--copier/-C' flags.

    If you just want a very simple, language-specific starting template, use the
    '--starter/-s' option.

    By default, pytoil will initialise a local git repo in the folder and commit it,
    following the style of modern language build tools such as rust's cargo. You can disable
    this behaviour by setting 'git' to false in pytoil's config file
    or by passing the '--no-git/-n' flag here.

    If you want pytoil to create a new virtual environment for your project, you
    can use the '--venv/-v' flag. Standard python and conda virtual environments
    are supported.

    If the '--venv/-v' flag is used, you may also pass a list of python packages
    to install into the created virtual environment. These will be delegated to
    the appropriate tool (pip or conda) depending on what environment was created.
    If the environment is conda, the packages will be passed at environment creation
    time meaning they will have their dependencies resolved together. Normal python
    environments will first be created and then have specified packages installed.

    If 'common_packages' is specified in pytoil's config file, these will automatically
    be included in the environment.

    To specify versions of packages via the command line, you must enclose them
    in double quotes e.g. "flask>=1.0.0" not flask>=1.0.0 otherwise this will
    be interpreted by the shell as a command redirection.

    Examples:

    $ pytoil new my_project

    $ pytoil new my_project --cookie https://github.com/some/cookie.git

    $ pytoil new my_project --venv conda

    $ pytoil new my_project -c https://github.com/some/cookie.git -v conda --no-git

    $ pytoil new my_project -v venv requests "flask>=1.0.0"

    $ pytoil new my_project --starter python
    """
    api = API(username=config.username, token=config.token)
    repo = Repo(
        owner=config.username,
        name=project,
        local_path=config.projects_dir.joinpath(project),
    )
    git = Git()

    # Additional packages to include
    to_install: list[str] = [*packages] + config.common_packages

    # Can't use --cookie and --starter
    if cookie and starter:
        printer.error("--cookie and --starter are mutually exclusive", exits=1)

    # Can't use --copier and --starter
    if _copier and starter:
        printer.error("--copier and --starter are mutually exclusive", exits=1)

    # Can't use --venv with non-python starters
    if (
        starter is not None  # User specified --starter
        and starter != "python"  # Requested starter is not python
        and venv is not None  # And the user wants a virtual environment
    ):
        printer.error(f"Can't create a venv for a {starter} project", exits=1)

    # Resolve config vs flag for no-git
    # flag takes priority over config
    use_git: bool = config.git and not no_git

    # Does this project already exist?
    # Mightaswell check concurrently
    local = repo.exists_local()
    remote = repo.exists_remote(api)

    if local:
        printer.error(f"{repo.name} already exists locally.")
        printer.note(
            f"To checkout this project, use `pytoil checkout {repo.name}`.", exits=1
        )

    if remote:
        printer.error(f"{repo.name} already exists on GitHub.")
        printer.note(
            f"To checkout this project, use `pytoil checkout {repo.name}`.", exits=1
        )

    # If we get here, we're good to create a new project
    if cookie:
        printer.info(f"Creating {repo.name} from cookiecutter: {cookie}.")
        cookiecutter(template=cookie, output_dir=str(config.projects_dir))

    elif _copier:
        printer.info(f"Creating {repo.name} from copier: {_copier}.")
        copier.run_auto(src_path=_copier, dst_path=repo.local_path)

    elif starter == "go":
        printer.info(f"Creating {repo.name} from starter: {starter}.")
        go_starter = GoStarter(path=config.projects_dir, name=repo.name)

        try:
            go_starter.generate(username=config.username)
        except GoNotInstalledError:
            printer.error("Go not installed.", exits=1)
        else:
            if use_git:
                git.init(cwd=repo.local_path, silent=False)
                git.add(cwd=repo.local_path, silent=False)
                git.commit(cwd=repo.local_path, silent=False)

    elif starter == "python":
        printer.info(f"Creating {repo.name} from starter: {starter}.")
        python_starter = PythonStarter(path=config.projects_dir, name=repo.name)
        python_starter.generate()
        if use_git:
            git.init(cwd=repo.local_path, silent=False)
            git.add(cwd=repo.local_path, silent=False)
            git.commit(cwd=repo.local_path, silent=False)

    elif starter == "rust":
        printer.info(f"Creating {repo.name} from starter: {starter}.")
        rust_starter = RustStarter(path=config.projects_dir, name=repo.name)

        try:
            rust_starter.generate()
        except CargoNotInstalledError:
            printer.error("Cargo not installed.", exits=1)
        else:
            if use_git:
                git.init(cwd=repo.local_path, silent=False)
                git.add(cwd=repo.local_path, silent=False)
                git.commit(cwd=repo.local_path, silent=False)

    else:
        # Just a blank new project
        printer.info(f"Creating {repo.name} at '{repo.local_path}'.")
        repo.local_path.mkdir()
        if use_git:
            git.init(cwd=repo.local_path, silent=False)

    # Now we need to handle any requested virtual environments
    if venv == "venv":
        printer.info(f"Creating virtual environment for {repo.name}")
        if to_install:
            printer.note(f"Including {', '.join(to_install)}")

        env = Venv(root=repo.local_path)
        with printer.progress() as p:
            p.add_task("[bold white]Working")
            env.create(packages=to_install, silent=True)

    elif venv == "conda":
        # Note, conda installs take longer so by default we don't hide the output
        # like we do for normal python environments
        printer.info(f"Creating conda environment for {repo.name}")
        if to_install:
            printer.note(f"Including {', '.join(to_install)}")

        conda_env = Conda(
            root=repo.local_path, environment_name=repo.name, conda=config.conda_bin
        )
        try:
            conda_env.create(packages=to_install)
        except EnvironmentAlreadyExistsError:
            printer.error(
                f"Conda environment {conda_env.environment_name!r} already exists",
                exits=1,
            )
        else:
            # Export the environment.yml
            conda_env.export_yml()

    # Now handle opening in an editor
    if config.specifies_editor():
        printer.sub_info(f"Opening {repo.name} with {config.editor}")
        editor.launch(path=repo.local_path, bin=config.editor)
Beispiel #20
0
def test_install_raises_if_conda_not_installed():

    conda = Conda(root=Path("somewhere"), environment_name="testy", conda=None)

    with pytest.raises(CondaNotInstalledError):
        conda.install(packages=["black", "mypy", "isort"])
Beispiel #21
0
def test_create_raises_if_conda_not_installed():
    conda = Conda(root=Path("somewhere"), environment_name="test", conda=None)

    with pytest.raises(CondaNotInstalledError):
        conda.create()
Beispiel #22
0
def test_export_yml_raises_if_conda_not_installed():

    conda = Conda(root=Path("somewhere"), environment_name="testy", conda=None)

    with pytest.raises(CondaNotInstalledError):
        conda.export_yml()