def publish_package( build_system: BuildSystem = typer.Option(get_default_package_build_system), python_version: Optional[str] = None, build: bool = typer.Option(False, "--build", "-b"), 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" ), yes: bool = build_yes_option(), ): with auto_confirm_yes(yes): if build: build_package(build_system=build_system, python_version=python_version) if build_system == BuildSystem.POETRY: with cd(PyProject.get().config_path.parent): repository_url = ( repository_url or PyProject.get().senv.package.poetry_publish_repository ) if repository_url is not None: subprocess.check_call( [ PyProject.get().poetry_path, "config", f"repositories.senv_{PyProject.get().package_name}", repository_url, ] ) args = [PyProject.get().poetry_path, "publish"] if username and password: args += ["--username", username, "--password", password] subprocess.check_call(args) elif build_system == BuildSystem.CONDA: with cd(PyProject.get().config_path.parent): repository_url = ( repository_url or PyProject.get().senv.package.conda_publish_url ) if repository_url is None: # todo add logic to publish to conda-forge raise NotImplementedError( "repository_url is required to publish a conda environment. " ) publish_conda(username, password, repository_url) else: raise NotImplementedError()
def test_lock_throws_if_not_all_platform_exists(temp_appdirs_pyproject, cli_runner, fake_combined_lock): with cd(temp_appdirs_pyproject.parent): fake_linux_lock_path = Path("my_lock_file_linux-64.lock") fake_linux_lock_path.write_text(fake_combined_lock.json(indent=2)) result = cli_runner.invoke( app, [ "package", "-f", str(temp_appdirs_pyproject), "lock", "--platforms", "osx-64", "--platforms", "win-64", "--platforms", "linux-64", "--based-on-tested-lock-file", str(fake_linux_lock_path.resolve()), ], ) assert result.exit_code != 0 assert isinstance(result.exception, SenvNotAllPlatformsInBaseLockFile) assert set(result.exception.missing_platforms) == {"osx-64", "win-64"}
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()
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()
def test_build_simple_pyproject_with_conda_even_with_poetry_build_system_in_pyproject( temp_simple_pyproject, cli_runner): with cd(temp_simple_pyproject.parent): result = cli_runner.invoke( app, [ "package", "-f", str(temp_simple_pyproject), "build", ], input="y", catch_exceptions=False, ) assert result.exit_code == 0, result.exception
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)
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()
def appdirs_env_lock_path(temp_appdirs_pyproject) -> Path: with cd(temp_appdirs_pyproject.parent): # env lock first to get the env locks that we will use to tests our code result = CliRunner().invoke( app, [ "env", "-f", str(temp_appdirs_pyproject), "lock", "--platforms", "linux-64", ], catch_exceptions=False, ) assert result.exit_code == 0 yield PyProject.get().senv.env.conda_lock_path
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()
def test_lock_appdirs_simple_includes_metadata(temp_appdirs_pyproject, cli_runner): with cd(temp_appdirs_pyproject.parent): cli_runner.invoke( app, [ "package", "-f", str(temp_appdirs_pyproject), "lock", "--platforms", "osx-64", ], catch_exceptions=False, ) conda_lock = CombinedCondaLock.parse_file( PyProject.get().senv.package.conda_lock_path) assert conda_lock.metadata.package_name == "appdirs" assert conda_lock.metadata.entry_points == []
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()
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()
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()
def lock( 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", ), ): c = PyProject.get() if build_system == BuildSystem.POETRY: with cd(c.config_path.parent): subprocess.check_call([c.poetry_path, "lock"]) elif build_system == BuildSystem.CONDA: c.env.conda_lock_path.parent.mkdir(exist_ok=True, parents=True) combined_lock = generate_combined_conda_lock_file( platforms, pyproject_to_conda_env_dict(), ) c.env.conda_lock_path.write_text(combined_lock.json(indent=2)) else: raise NotImplementedError()
def test_env_locks_builds_the_lock_files_in_default_env_lock_files( temp_pyproject, cli_runner ): with cd(temp_pyproject.parent): result = cli_runner.invoke( app, [ "env", "-f", str(temp_pyproject), "lock", "--platforms", "linux-64", ], catch_exceptions=False, ) assert result.exit_code == 0 lock_file = PyProject.get().senv.env.__fields__["conda_lock_path"].default assert lock_file.exists() combined_lock = CombinedCondaLock.parse_file(lock_file) assert set(combined_lock.platform_tar_links.keys()) == {"linux-64"}
def shell(build_system: BuildSystem = typer.Option( get_default_env_build_system)): c = PyProject.get() # conda activate does not work using the conda executable path (I am not sure why) # force adding the conda executable to the path and then call it environ[ "PATH"] = f"{c.conda_path.parent}{os.path.pathsep}{environ.get('PATH')}" if build_system == BuildSystem.POETRY: cwd = os.getcwd() with cd(c.config_path.parent): with spawn_shell(command="poetry shell", cwd=cwd): pass elif build_system == BuildSystem.CONDA: with spawn_shell( command= f"{shlex.quote(str(c.conda_path.name))} activate {c.env.name}", ): pass else: raise NotImplementedError() environ["PATH"] = os.path.pathsep.join( environ.get("PATH").split(os.path.pathsep)[1:])
def test_publish_requires_username_and_password(temp_small_conda_pyproject, cli_runner): args = [ "package", "-f", str(temp_small_conda_pyproject), "publish", ] with cd(temp_small_conda_pyproject.parent): result = cli_runner.invoke( app, args, ) assert result.exit_code == 2, result.output result = cli_runner.invoke( app, args + ["-u", "username"], ) assert result.exit_code == 2, result.output result = cli_runner.invoke( app, args + ["-p", "password"], ) assert result.exit_code == 2, result.output