Esempio n. 1
0
def _parse_pyproject_toml(platform: str,
                          include_dev_dependencies: bool) -> LockSpecification:
    specs: List[str] = []
    deps = PyProject.get().senv.dependencies
    if include_dev_dependencies:
        deps.update(PyProject.get().senv.dev_dependencies)

    for depname, depattrs in deps.items():
        conda_dep_name = normalize_pypi_name(depname)
        if isinstance(depattrs, Mapping):
            poetry_version_spec = depattrs["version"]
            # TODO: support additional features such as markers for things like sys_platform, platform_system
        elif isinstance(depattrs, str):
            poetry_version_spec = depattrs
        else:
            raise TypeError(
                f"Unsupported type for dependency: {depname}: {depattrs:r}")
        conda_version = poetry_version_to_conda_version(poetry_version_spec)
        spec = to_match_spec(conda_dep_name, conda_version)

        if conda_dep_name == "python":
            specs.insert(0, spec)
        else:
            specs.append(spec)

    return LockSpecification(specs=specs,
                             channels=PyProject.get().senv.conda_channels,
                             platform=platform)
Esempio n. 2
0
def pyproject_callback(pyproject_file: Path = typer.Option(Path(".") /
                                                           "pyproject.toml",
                                                           "-f",
                                                           "--pyproject-file",
                                                           exists=True)):
    PyProject.read_toml(pyproject_file)
    chdir(PyProject.get().config_path.parent)
def test_lock_based_on_tested_includes_pinned_dependencies(
        temp_appdirs_pyproject, cli_runner, appdirs_env_lock_path):
    with cd(temp_appdirs_pyproject.parent):
        click_line = next(
            l for l in appdirs_env_lock_path.read_text().splitlines()
            if "click" in l)

        result = cli_runner.invoke(
            app,
            [
                "package",
                "-f",
                str(temp_appdirs_pyproject),
                "lock",
                "--platforms",
                "linux-64",
                "--based-on-tested-lock-file",
                str(appdirs_env_lock_path.resolve()),
            ],
            catch_exceptions=False,
        )
        assert result.exit_code == 0
        assert PyProject.get().senv.package.conda_lock_path.exists()
        assert click_line in PyProject.get(
        ).senv.package.conda_lock_path.read_text()
Esempio n. 4
0
def test_senv_overrides_poetry():
    config_dict = {
        "tool": {
            "poetry": {
                "version": "poetry1",
                "description": "poetry2",
                "name": "poetry3",
            },
            "senv": {
                "version": "senv1",
                "description": "senv2",
                "name": "senv3",
            },
        }
    }
    config = PyProject(**config_dict)
    assert config.version == "senv1"
    assert config.senv.description == "senv2"
    assert config.package_name == "senv3"

    # without senv, it should use the poetry information
    del config_dict["tool"]["senv"]
    config = PyProject(**config_dict)
    assert config.version == "poetry1"
    assert config.senv.description == "poetry2"
    assert config.package_name == "poetry3"
def test_pyproject_to_conda_creates_recipe_right_params():
    PyProject.read_toml(SIMPLE_PYPROJECT_TOML)
    recipe = pyproject_to_meta()
    # it should ignore the dev-environments
    assert recipe.package.name == "test_name"
    assert recipe.package.version == "0.1.0"
    assert "python" in recipe.requirements.host[0]
    assert "3.7.0" in recipe.requirements.host[0]
Esempio n. 6
0
def pyproject_to_conda_env_dict() -> Dict:
    channels = PyProject.get().senv.conda_channels
    dependencies = _get_dependencies_from_pyproject(
        include_dev_dependencies=True)

    return dict(name=PyProject.get().env.name,
                channels=channels,
                dependencies=dependencies)
def test_pyproject_to_conda_dev_env_dict_generates_env_with_dev_deps():
    PyProject.read_toml(SIMPLE_PYPROJECT_TOML)
    env_dict = pyproject_to_conda_env_dict()
    dep_names = [d.split(" ")[0] for d in env_dict["dependencies"]]

    assert len(env_dict["dependencies"]) == 8
    # it should not ignore the dev-environments
    assert "python" in dep_names
    assert "ensureconda" in dep_names
    assert "appdirs" in dep_names
    assert "pyinstaller" in dep_names
    assert "pytest" in dep_names
Esempio n. 8
0
def test_env_name_default_to_package_name():
    config_dict: Dict[str, Any] = {
        "tool": {
            "senv": {
                "name": "p_name",
            },
        }
    }
    config = PyProject(**config_dict)
    assert config.env.name == "p_name"
    config_dict["tool"]["senv"]["env"] = {"name": "env_name"}
    config2 = PyProject(**config_dict)
    assert config2.env.name == "env_name"
def test_pyproject_to_conda_creates_recipe_with_deps():
    PyProject.read_toml(SIMPLE_PYPROJECT_TOML)
    recipe = pyproject_to_meta()
    deps = recipe.requirements.run
    dep_names = [d.split(" ")[0] for d in deps]
    # it should ignore the dev-environments
    assert len(deps) == 6
    assert "python" in dep_names
    assert "ensureconda" in dep_names
    assert "click" in dep_names
    assert "tomlkit" in dep_names
    assert "appdirs" in dep_names
    assert "conda-lock" in dep_names
Esempio n. 10
0
def test_conda_build_dir_can_not_be_in_project(tmp_path):
    tmp_toml = tmp_path / "tmp_toml"
    config_dict = {
        "tool": {
            "senv": {
                "name": "senv3",
                "package": {
                    "conda-build-path": "./dist",
                },
            },
        }
    }
    tmp_toml.write_text(toml.dumps(config_dict))

    with cd(tmp_path), pytest.raises(SenvBadConfiguration):
        PyProject.read_toml(tmp_toml)
Esempio n. 11
0
def sync(build_system: BuildSystem = typer.Option(get_default_env_build_system)
         ):
    c = PyProject.get()
    if build_system == BuildSystem.POETRY:
        with cd(c.config_path.parent):
            subprocess.check_call(
                [c.poetry_path, "install", "--remove-untracked"])
    elif build_system == BuildSystem.CONDA:
        if not c.env.conda_lock_path.exists():
            log.info("No lock file found, locking environment now")
            lock(build_system=build_system, platforms=get_conda_platforms())
        with c.env.platform_conda_lock as lock_file:
            result = subprocess.run([
                str(c.conda_path),
                "create",
                "--file",
                str(lock_file.resolve()),
                "--yes",
                "--name",
                c.env.name,
            ])
        if result.returncode != 0:
            raise typer.Abort("Failed syncing environment")
    else:
        raise NotImplementedError()
Esempio n. 12
0
def test_remove_config_key_removes_it_from_file(temp_pyproject, cli_runner):
    cli_runner.invoke(
        app,
        [
            "config",
            "set",
            "env.build-system",
            "poetry",
            "-f",
            str(temp_pyproject),
        ],
    )

    cli_runner.invoke(
        app,
        [
            "config",
            "remove",
            "env.build-system",
            "-f",
            str(temp_pyproject),
        ],
    )

    assert (PyProject.read_toml(temp_pyproject).senv.env.build_system ==
            BuildSystem.CONDA)
Esempio n. 13
0
def update(
    build_system: BuildSystem = typer.Option(get_default_env_build_system),
    platforms: List[str] = typer.Option(
        get_conda_platforms,
        case_sensitive=False,
        help="conda platforms, for example osx-64 or linux-64",
    ),
):
    if build_system == BuildSystem.POETRY:
        with cd(PyProject.get().config_path.parent):
            subprocess.check_call([PyProject.get().poetry_path, "update"])
    elif build_system == BuildSystem.CONDA:
        lock(build_system=build_system, platforms=platforms)
        sync(build_system=build_system)

    else:
        raise NotImplementedError()
Esempio n. 14
0
 def _build_temp_pyproject(pyproject_path: Path):
     temp_path = tmp_path / "pyproject.toml"
     copyfile(pyproject_path, temp_path)
     c = PyProject.read_toml(temp_path)
     project = tmp_path / c.package_name.replace("-", "_") / "main.py"
     project.parent.mkdir(parents=True, exist_ok=True)
     project.write_text("print('hello world')")
     return temp_path
Esempio n. 15
0
def test_config_build_system_has_to_be_enum():
    config_dict = {
        "tool": {
            "senv": {
                "name": "test_name",
                "env": {
                    "build-system": "poetry"
                }
            }
        }
    }
    config = PyProject(**config_dict)
    assert config.senv.env.build_system == BuildSystem.POETRY

    config_dict["tool"]["senv"]["env"]["build-system"] = "no_build_system"
    with pytest.raises(ValueError):
        PyProject(**config_dict)
Esempio n. 16
0
def run(
        ctx: typer.Context,
        build_system: BuildSystem = typer.Option(get_default_env_build_system),
):
    if build_system == BuildSystem.POETRY:
        with cd(PyProject.get().config_path.parent):
            subprocess.check_call(["poetry", "run"] + ctx.args)
    elif build_system == BuildSystem.CONDA:
        subprocess.check_call([
            "conda",
            "run",
            "-n",
            PyProject.get().env.name,
            "--no-capture-output",
            "--live-stream",
        ] + ctx.args)
    else:
        raise NotImplementedError()
Esempio n. 17
0
def test_set_config_add_value_to_pyproject(temp_pyproject, cli_runner):
    result = cli_runner.invoke(
        app,
        [
            "config",
            "-f",
            str(temp_pyproject),
            "set",
            "env.conda-lock-platforms",
            "linux-64",
        ],
        catch_exceptions=False,
    )

    assert result.exit_code == 0

    PyProject.read_toml(temp_pyproject)
    assert PyProject.get().senv.env.conda_lock_platforms == {"linux-64"}
Esempio n. 18
0
def test_set_config_with_wrong_value_does_not_change_pyproject(
        temp_pyproject, cli_runner):
    original_config = PyProject.read_toml(temp_pyproject).dict()
    cli_runner.invoke(
        app,
        [
            "-f",
            str(temp_pyproject),
            "config",
            "set",
            "conda-path",
            "none_existing_path",
        ],
        catch_exceptions=False,
    )

    new_config = PyProject.read_toml(temp_pyproject).dict()
    assert new_config == original_config
Esempio n. 19
0
def publish_locked_package(
    build_system: BuildSystem = typer.Option(get_default_package_build_system),
    repository_url: Optional[str] = None,
    username: str = typer.Option(
        ..., "--username", "-u", envvar="SENV_PUBLISHER_USERNAME"
    ),
    password: str = typer.Option(
        ..., "--password", "-p", envvar="SENV_PUBLISHER_PASSWORD"
    ),
    lock_file: Path = typer.Option(
        lambda: PyProject.get().senv.package.conda_lock_path,
        "--lock-file",
        "-l",
        exists=True,
    ),
    yes: bool = build_yes_option(),
):
    c: PyProject = PyProject.get()
    with auto_confirm_yes(yes):
        if build_system == BuildSystem.POETRY:
            raise NotImplementedError("publish locked ")
        elif build_system == BuildSystem.CONDA:
            with cd_tmp_dir() as tmp_dir:
                meta_path = tmp_dir / "conda.recipe" / "meta.yaml"
                temp_lock_path = meta_path.parent / "package_locked_file.lock.json"
                meta_path.parent.mkdir(parents=True)
                shutil.copyfile(
                    str(lock_file.absolute()), str(temp_lock_path.absolute())
                )

                locked_package_to_recipe_yaml(temp_lock_path, meta_path)
                build_conda_package_from_recipe(meta_path.absolute())

                with cd(meta_path.parent):
                    repository_url = repository_url or c.senv.package.conda_publish_url
                    publish_conda(
                        username,
                        password,
                        repository_url,
                        package_name=c.package_name_locked,
                    )
        else:
            raise NotImplementedError()
Esempio n. 20
0
def set_new_setting_value(
    key: AllowedConfigKeys = typer.Argument(...),
    value: str = typer.Argument(
        None,
        help=
        "Value of the setting. For multi value setting like the conda-platforms,"
        " separate them with a comma ','",
    ),
):
    set_config_value_to_pyproject(PyProject.get().config_path, key, value)
Esempio n. 21
0
def test_lock_appdirs_simple_does_not_include_fake_dependencies(
        temp_appdirs_pyproject, cli_runner):
    with cd(temp_appdirs_pyproject.parent):
        result = cli_runner.invoke(
            app,
            [
                "package",
                "-f",
                str(temp_appdirs_pyproject),
                "lock",
                "--platforms",
                "linux-64",
            ],
            catch_exceptions=False,
        )
        assert result.exit_code == 0
        assert PyProject.get().senv.package.conda_lock_path.exists()
        assert "click" not in PyProject.get(
        ).senv.package.conda_lock_path.read_text()
Esempio n. 22
0
def lock_app(
    build_system: BuildSystem = typer.Option(get_default_package_build_system),
    platforms: List[str] = typer.Option(
        get_conda_platforms,
        case_sensitive=False,
        help="conda platforms, for example osx-64 and/or linux-64",
    ),
    based_on_tested_lock_file: Optional[Path] = based_on_tested_lock_file_option,
    conda_channels: Optional[List[str]] = typer.Option(
        get_conda_channels,
    ),
    output: Path = typer.Option(
        lambda: PyProject.get().senv.package.conda_lock_path, "--output", "-o"
    ),
):
    c = PyProject.get()
    platforms = platforms
    if build_system == BuildSystem.POETRY:
        raise NotImplementedError()
    elif build_system == BuildSystem.CONDA:
        output.parent.mkdir(exist_ok=True, parents=True)
        if based_on_tested_lock_file is None:
            combined_lock = generate_combined_conda_lock_file(
                platforms,
                dict(
                    name=c.package_name,
                    channels=conda_channels,
                    dependencies={c.package_name: f"=={c.version}"},
                ),
            )
            output.write_text(combined_lock.json(indent=2))

        else:
            combined_lock = generate_app_lock_file_based_on_tested_lock_path(
                lock_path=based_on_tested_lock_file,
                conda_channels=conda_channels,
                platforms=platforms,
            )

            output.write_text(combined_lock.json(indent=2))
        log.info(f"Package lock file generated in {output.resolve()}")
    else:
        raise NotImplementedError()
Esempio n. 23
0
def build_package(
    build_system: BuildSystem = typer.Option(get_default_package_build_system),
    python_version: Optional[str] = None,
):
    # todo add progress bar
    if build_system == BuildSystem.POETRY:
        with cd(PyProject.get().config_path.parent):
            subprocess.check_call([PyProject.get().poetry_path, "build"])
    elif build_system == BuildSystem.CONDA:
        with tmp_env():
            meta_path = (
                PyProject.get().config_path.parent / "conda.recipe" / "meta.yaml"
            )
            pyproject_to_recipe_yaml(
                python_version=python_version,
                output=meta_path,
            )
            build_conda_package_from_recipe(meta_path, python_version)
    else:
        raise NotImplementedError()
Esempio n. 24
0
def build_conda_package_from_recipe(meta_path: Path,
                                    python_version: Optional[str] = None):
    set_conda_build_path()
    if which("conda-mambabuild") is None:
        _install_package_dependencies()
    args = ["conda-mambabuild", "--build-only", "--override-channels"]
    for c in PyProject.get().senv.conda_channels:
        args += ["--channel", c]
    if python_version:
        args.extend(["--python", python_version])
    result = subprocess.run(args + [str(meta_path.parent)])
    if result.returncode != 0:
        raise typer.Abort("Failed building conda package")
Esempio n. 25
0
def pyproject_to_meta(
    *,
    python_version: Optional[str] = None,
) -> CondaMeta:
    """
    :param python_version: python version used to create the conda meta file
    """
    dependencies = _get_dependencies_from_pyproject(
        include_dev_dependencies=False)
    python_version = _populate_python_version(python_version, dependencies)
    if python_version != PyProject.get().python_version:
        log.warning(
            "Python version in the pyproject.toml is different than the one provided"
        )
    if python_version is None:
        raise SenvInvalidPythonVersion(
            f"No python version provided or defined in {PyProject.get().config_path}"
        )

    c: PyProject = PyProject.get()
    license = c.senv.license if c.senv.license != "Proprietary" else "INTERNAL"
    entry_points = [
        f"{name} = {module}" for name, module in c.senv.scripts.items()
    ]

    return CondaMeta(
        package=_Package(name=c.package_name, version=c.version),
        source=_Source(path=c.config_path.parent.resolve()),
        build=_Build(entry_points=entry_points),
        requirements=_Requirements(host=[python_version, "pip", "poetry"],
                                   run=dependencies),
        about=_About(
            home=c.senv.homepage,
            license=license,
            description=c.senv.description,
            doc_url=c.senv.documentation,
        ),
        extra=_Extra(maintainers=c.senv.authors),
    )
Esempio n. 26
0
def _validate_toml(toml):
    try:
        c = PyProject(**toml)
        c._config_path = PyProject.get().config_path
        c.validate_fields()
    except ValidationError as e:
        log.error(str(e))
        raise typer.Abort()
Esempio n. 27
0
def test_env_locks_builds_the_lock_files_in_the_configured_directory(
    temp_pyproject, cli_runner
):
    lock_file = temp_pyproject.parent / "my_lock_folder"
    PyProject.read_toml(temp_pyproject)
    set_new_setting_value(AllowedConfigKeys.CONDA_ENV_LOCK_PATH, str(lock_file))
    result = cli_runner.invoke(
        app,
        [
            "env",
            "-f",
            str(temp_pyproject),
            "lock",
            "--platforms",
            "osx-64",
        ],
        catch_exceptions=False,
    )
    assert result.exit_code == 0
    assert lock_file.exists()
    combined_lock = CombinedCondaLock.parse_file(lock_file)

    assert set(combined_lock.platform_tar_links.keys()) == {"osx-64"}
Esempio n. 28
0
def test_config_conda_and_poetry_path_have_to_be_executable(key):
    config_dict = {
        "tool": {
            "senv": {
                key: str(Path(__file__)),
            }
        }
    }
    try:
        PyProject(**config_dict)
        pytest.fail("config should raise exception as path is not executable")
    except ValueError as e:
        assert "not executable" in str(e).lower()
        assert "not found" not in str(e).lower()
Esempio n. 29
0
def test_config_conda_and_poetry_path_have_to_exists(key):
    config_dict = {
        "tool": {
            "senv": {
                key: str(Path("/no/real/path")),
            }
        }
    }
    try:
        PyProject(**config_dict)
        pytest.fail("config should raise exception as paths do not exists")
    except ValueError as e:
        assert "not found" in str(e).lower()
        assert "not executable" not in str(e).lower()
Esempio n. 30
0
def _add_app_lockfile_metadata(lockfile: Path):
    lock_content = lockfile.read_text()
    if "@EXPLICIT" not in lock_content:
        raise SenvxMalformedAppLockFile("No @EXPLICIT found in lock file")
    lock_header, tars = lock_content.split("@EXPLICIT", 1)
    c = PyProject.get()
    metadata = LockFileMetaData(
        package_name=c.package_name,
        entry_points=list(c.senv.scripts.keys()),
    )
    meta_json = (
        "\n".join([f"# {l}"
                   for l in metadata.json(indent=2).splitlines()]) + "\n")
    lockfile.write_text(lock_header + "# @METADATA_INIT\n" + meta_json +
                        "# @METADATA_END\n" + "@EXPLICIT\n" + tars)