def infer(cls, name: str, packages: Packages, channels: ListLike = None): """create conda_env_tracker environment by inferring to existing conda environment""" if name == "base": raise CondaEnvTrackerCondaError( "Environment can not be created using default name base" ) if name not in get_all_existing_environment(): raise CondaEnvTrackerCondaError( f"Environment {name} can not be inferred, does not exist" ) dependencies = get_dependencies(name=name) if "r-base" in dependencies["conda"]: dependencies["r"] = get_r_dependencies(name=name) user_packages = {"conda": Packages(), "pip": Packages()} for package in packages: if package.name in dependencies.get("conda", Packages()): user_packages["conda"].append(package) elif package.name in dependencies.get("pip", Packages()): user_packages["pip"].append(package) else: raise CondaEnvTrackerCondaError( f"Environment {name} does not have {package.spec} installed" ) conda_create_cmd = get_conda_create_command( name, user_packages["conda"], channels ) specs = Actions.get_package_specs( packages=user_packages["conda"], dependencies=dependencies["conda"] ) history = History.create( name=name, channels=Channels(channels), packages=PackageRevision.create( user_packages["conda"], dependencies=dependencies ), logs=Logs(conda_create_cmd), actions=Actions.create(name=name, specs=specs, channels=Channels(channels)), diff=Diff.create( packages=user_packages["conda"], dependencies=dependencies ), debug=Debug.create(name=name), ) env = cls(name=name, history=history, dependencies=dependencies) if user_packages["pip"]: handler = PipHandler(env=env) handler.update_history_install(packages=user_packages["pip"]) env = handler.env env.export() return env
def setup(mocker, request): """Set up for diff function""" local_packages = request.param["local_packages"] local_logs = request.param["local_logs"] local_actions = request.param["local_actions"] env_name = request.param["env_name"] remote_packages = request.param["remote_packages"] remote_logs = request.param["remote_logs"] remote_actions = request.param["remote_actions"] dependencies = { "conda": { "pandas": Package("pandas", "pandas", version="0.23.0"), "pytest": Package("pytest", "pytest", version="4.0.0"), } } mocker.patch( "conda_env_tracker.env.get_dependencies", mocker.Mock(return_value=dependencies) ) history = History.create( name=env_name, packages=PackageRevision.create(local_packages, dependencies=dependencies), channels=Channels(["conda-forge"]), logs=Logs([log for log in local_logs]), actions=local_actions, diff=Diff(), debug=Debug(), ) env = Environment(name=env_name, history=history) mocker.patch("conda_env_tracker.main.Environment.read", return_value=env) mocker.patch( "conda_env_tracker.gateways.io.EnvIO.get_remote_dir", return_value="~/path/to/remote", ) mocker.patch("pathlib.Path.is_dir", return_value=True) history = History.create( name=env_name, channels=Channels(["conda-forge"]), packages=PackageRevision.create(remote_packages, dependencies=dependencies), logs=Logs([log for log in remote_logs]), actions=remote_actions, diff=Diff(), debug=Debug(), ) mocker.patch( "conda_env_tracker.gateways.io.EnvIO.get_history", return_value=history ) mocker.patch("pathlib.Path.write_text") yield env, request.param["push_should_fail"] if (USER_ENVS_DIR / env_name).exists(): shutil.rmtree(USER_ENVS_DIR / env_name)
def test_get_packages_with_version_spec(mocker): mocker.patch( "conda_env_tracker.env.get_dependencies", return_value={ "conda": { "pandas": Package("pandas", "pandas", "0.23", "py_36"), "numpy": Package("numpy", "numpy", "0.13", "py_36"), }, "pip": { "pytest": Package("pytest", "pytest", "4.0") }, }, ) local_packages = HistoryPackages.create( (Package.from_spec("pandas=0.23"), )) local_packages.update_packages( packages=(Package.from_spec("pytest==4.0"), ), source="pip") local_history = History( name=ENV_NAME, packages=local_packages, channels=Channels("defaults"), logs=Logs([]), actions=Actions(), debug=Debug(), ) env = Environment(name=ENV_NAME, history=local_history) expected = { "conda": [Package("pandas", "pandas=0.23", "0.23", "py_36")], "pip": [Package("pytest", "pytest==4.0", "4.0")], } actual = get_packages(env) assert actual == expected
def _update_history( self, get_command: Callable, packages: Packages, channels: ListLike, strict_channel_priority: bool = True, ): self.env.update_dependencies() self.env.history.update_packages(packages=packages, dependencies=self.env.dependencies) self.env.validate_packages(packages) log = get_command(name=self.env.name, packages=packages) if channels: log = log + " " + Channels.format_channels(channels) specs = self.env.history.actions.get_package_specs( packages=packages, dependencies=self.env.dependencies["conda"]) channel_string = self.env.history.channels.create_channel_command( preferred_channels=channels, strict_channel_priority=strict_channel_priority) command_with_specs = get_command(name=self.env.name, packages=Packages.from_specs(specs)) action = f"{command_with_specs} {channel_string}" self.env.history.append(log=log, action=action)
def test_export_success(mocker): """Test package export""" mocker.patch( "conda_env_tracker.history.history.uuid4").return_value = "unique_uuid" expected = { "name": "environment-name", "id": "unique_uuid", "history-file-version": "1.0", "channels": ["conda-forge", "main"], "packages": { "conda": { "pytest": "*" } }, "revisions": [{ "log": "conda create --name test pytest", "action": "conda create --name test pytest=4.0=py36_0 " "--channel conda-forge " "--channel main", "debug": { "platform": "osx", "conda_version": "4.5.10" }, "packages": { "conda": { "pytest": "*" } }, "diff": { "conda": { "upsert": ["pytest=4.0"] } }, }], } actual = History.create( name="environment-name", channels=Channels(["conda-forge", "main"]), packages=PackageRevision(conda=dict( pytest=Package.from_spec("pytest"))), logs=Logs(["conda create --name test pytest"]), actions=Actions([ "conda create --name test pytest=4.0=py36_0 " "--channel conda-forge " "--channel main" ]), diff=Diff(conda=dict(upsert=dict( pytest=Package("pytest", version="4.0")))), debug=[{ "platform": "osx", "conda_version": "4.5.10" }], ).export() assert expected == actual
def test_ignore_empty_pip_packages(mocker): """When a user installs a pip package and then removes it, the pip packages is an empty dictionry which does not need to show up in the history file. """ mocker.patch( "conda_env_tracker.history.history.uuid4").return_value = "unique_uuid" expected = { "name": "environment-name", "id": "unique_uuid", "history-file-version": "1.0", "channels": ["main"], "packages": { "conda": { "pytest": "*" } }, "revisions": [{ "log": "conda create --name test pytest", "action": "conda create --name test pytest=4.0=py36_0 " "--channel main", "debug": { "platform": "osx", "conda_version": "4.5.10" }, "packages": { "conda": { "pytest": "*" } }, "diff": { "conda": { "upsert": ["pytest=4.0"] } }, }], } actual = History.create( name="environment-name", channels=Channels(["main"]), packages=PackageRevision( conda=dict(pytest=Package.from_spec("pytest")), pip={}), logs=Logs(["conda create --name test pytest"]), actions=Actions( ["conda create --name test pytest=4.0=py36_0 " "--channel main"]), diff=Diff( dict(conda=dict(upsert=dict( pytest=Package("pytest", version="4.0"))))), debug=[{ "platform": "osx", "conda_version": "4.5.10" }], ).export() assert expected == actual
def test_extract_packages_from_actions(spec): """Test parsing the packges from action item""" action = Actions.create( name="test-extract", specs=[spec], channels=Channels(["conda-forge", "conda-remote"]), ) packages = action.extract_packages(index=0) assert packages[0] == Package.from_spec(spec)
def test_get_create_action(): """Test create action string""" channels = Channels(["conda-forge", "main"]) expected = [( "conda create --name test-create python=3.6.6=py_36 pandas=0.23.1=py_36 " "--override-channels --strict-channel-priority " "--channel conda-forge " "--channel main")] actual = Actions.create("test-create", ["python=3.6.6=py_36", "pandas=0.23.1=py_36"], channels=channels) assert expected == actual
def expected_update(mocker): """Set up for update function""" packages = Packages.from_specs("pandas") channel = "conda-forge" create_cmd = "conda install pandas" name = "test-update" mocker.patch("conda_env_tracker.gateways.io.Path.mkdir") mocker.patch("conda_env_tracker.gateways.io.Path.write_text") mocker.patch("conda_env_tracker.gateways.io.Path.iterdir") mocker.patch("conda_env_tracker.history.debug.get_pip_version", return_value=None) dependencies = { "conda": { "pandas": Package("pandas", "pandas", "0.23", "py36"), "pytest": Package("pytest", "pytest", "0.1", "py36"), }, "pip": {}, } mocker.patch("conda_env_tracker.env.get_dependencies", return_value=dependencies) mocker.patch("conda_env_tracker.env.EnvIO.export_packages") history = History.create( name=name, channels=Channels([channel]), packages=PackageRevision.create(packages, dependencies=dependencies), logs=Logs(create_cmd), actions=Actions.create(name="test-update", specs=["pandas"], channels=Channels([channel])), diff=Diff.create(packages, dependencies=dependencies), debug=Debug(), ) mocker.patch( "conda_env_tracker.main.Environment.read", return_value=Environment(name=name, history=history), ) return history
def test_write_history_file(env_io): """Test to create history yaml file and test get history""" env_io.write_history_file( History.create( name="test-env", channels=Channels(["main"]), packages=PackageRevision( {"conda": { "python": Package.from_spec("python") }}), logs=Logs(["conda create -n test-env python"]), actions=Actions([ "conda create -n test-env python=3.7.3=buildstring --override-channels --channel main" ]), diff=Diff({ "conda": { "upsert": { "python": Package("python", version="3.7.3") } } }), debug=["blah"], )) assert ["main"] == env_io.get_history().channels assert { "conda": { "python": Package.from_spec("python") } } == env_io.get_history().packages assert (Revisions( **{ "logs": ["conda create -n test-env python"], "actions": [ "conda create -n test-env python=3.7.3=buildstring --override-channels --channel main" ], "packages": [{ "conda": { "python": Package.from_spec("python") } }], "diffs": [{ "conda": { "upsert": { "python": Package("python", "python", version="3.7.3") } } }], "debug": ["blah"], }) == env_io.get_history().revisions)
def parse(cls, history_content: dict): """Parse the history from the file.""" try: sections = { "name": history_content.get("name"), "id": history_content.get("id"), "channels": Channels(history_content.get("channels")), "revisions": Revisions.parse(history_content.get("revisions")), "packages": PackageRevision.parse(history_content.get("packages")), } return cls(**sections) except Exception as err: raise CondaEnvTrackerParseHistoryError( f"Failed to parse history with error: {err}" ) from err
def get_conda_create_command( name: str, packages: Packages, channels: ListLike = None, yes: bool = False, strict_channel_priority: bool = True, ) -> str: """Create the conda create command""" command = ["conda", "create"] if yes: command.append("-y") command.extend(["--name", name]) command.append(_join_packages(packages)) if channels: channel_command = Channels(channels).create_channel_command( strict_channel_priority=strict_channel_priority) command.append(channel_command) create_cmd = " ".join(command) return create_cmd
def test_get_packages_r(mocker): mocker.patch( "conda_env_tracker.env.get_dependencies", return_value={ "conda": { "r-base": Package("r-base", "r-base", "3.5.1", "h539"), "r-devtools": Package("r-devtools", "r-devtools", "0.1.0", "r351_0"), } }, ) mocker.patch( "conda_env_tracker.env.get_r_dependencies", return_value={ "trelliscopejs": Package("trelliscopejs", "trelliscopejs" "0.1.0") }, ) local_packages = HistoryPackages.create( Packages( (Package.from_spec("r-base"), Package.from_spec("r-devtools")))) local_packages.update_packages( packages=(Package.from_spec("trelliscopejs"), ), source="r") local_history = History( name=ENV_NAME, packages=local_packages, channels=Channels("defaults"), logs=Logs([]), actions=Actions(), debug=Debug(), ) env = Environment(name=ENV_NAME, history=local_history) expected = { "conda": [ Package("r-base", "r-base", "3.5.1", "h539"), Package("r-devtools", "r-devtools", "0.1.0", "r351_0"), ], "r": [Package("trelliscopejs", "trelliscopejs", "0.1.0")], } actual = get_packages(env) assert actual == expected
def create( cls, name: str, specs: ListLike, channels: Channels, yes: bool = False, strict_channel_priority: bool = True, ): """return the action string""" if not channels: raise CondaEnvTrackerChannelError("Could not find user channels.") channel_string = channels.create_channel_command( strict_channel_priority=strict_channel_priority) actions = cls() if yes: prefix_cmd = f"conda create -y --name {name} " else: prefix_cmd = f"conda create --name {name} " actions.append(prefix_cmd + " ".join(specs) + " " + channel_string) return actions
def update_history_remove(self, packages: Packages, channels: ListLike = None) -> None: """Update history for conda remove.""" self.env.update_dependencies() self.env.history.remove_packages(packages=packages, dependencies=self.env.dependencies) self.env.validate_packages() remove_command = get_conda_remove_command(name=self.env.name, packages=packages) if channels: log = remove_command + " " + Channels.format_channels(channels) else: log = remove_command channel_command = self._get_conda_remove_channel_command( channels=channels) action = f"{remove_command} {channel_command}" self.env.history.append(log=log, action=action)
def test_export_empty(mocker): """Test package export""" mocker.patch( "conda_env_tracker.history.history.uuid4").return_value = "unique_uuid" expected = { "name": "", "id": "unique_uuid", "history-file-version": "1.0", "channels": [], "packages": {}, "revisions": [], } actual = History.create( name="", channels=Channels(channels=[]), packages=PackageRevision(), logs=Logs(), actions=Actions([]), diff=Diff(), debug=[], ).export() assert expected == actual
def test_get_packages_conda(mocker): mocker.patch( "conda_env_tracker.env.get_dependencies", return_value={ "conda": { "pandas": Package("pandas", "pandas", "0.23", "py_36"), "numpy": Package("numpy", "numpy", "0.13", "py_36"), } }, ) packages = (Package.from_spec("pandas"), ) local_history = History( name=ENV_NAME, packages=HistoryPackages.create(packages), channels=Channels("defaults"), logs=Logs([]), actions=Actions(), debug=Debug(), ) env = Environment(name=ENV_NAME, history=local_history) expected = {"conda": [Package("pandas", "pandas", "0.23", "py_36")]} actual = get_packages(env) assert actual == expected
def test_tuple_to_list(): actual = Channels(("pro", "main")) expected = ["pro", "main"] assert actual == expected
def test_init_string(): actual = Channels("conda-forge") expected = ["conda-forge"] assert actual == expected
def create( cls, name: str, packages: Packages, channels: ListLike = None, yes: bool = False, strict_channel_priority: bool = True, ): """Creating a conda environment from a list of packages.""" if name == "base": raise CondaEnvTrackerCondaError( "Environment can not be created using default name base" ) if name in get_all_existing_environment(): message = ( f"This environment {name} already exists. Would you like to replace it" ) if prompt_yes_no(prompt_msg=message, default=False): delete_conda_environment(name=name) local_io = EnvIO(env_directory=USER_ENVS_DIR / name) if local_io.env_dir.exists(): local_io.delete_all() else: raise CondaEnvTrackerCondaError(f"Environment {name} already exists") logger.debug(f"creating conda env {name}") conda_create( name=name, packages=packages, channels=channels, yes=yes, strict_channel_priority=strict_channel_priority, ) create_cmd = get_conda_create_command( name=name, packages=packages, channels=channels, strict_channel_priority=strict_channel_priority, ) specs = Actions.get_package_specs( packages=packages, dependencies=get_dependencies(name=name)["conda"] ) if not channels: channels = get_conda_channels() dependencies = get_dependencies(name=name) history = History.create( name=name, channels=Channels(channels), packages=PackageRevision.create(packages, dependencies=dependencies), logs=Logs(create_cmd), actions=Actions.create( name=name, specs=specs, channels=Channels(channels), strict_channel_priority=strict_channel_priority, ), diff=Diff.create(packages=packages, dependencies=dependencies), debug=Debug.create(name=name), ) env = cls(name=name, history=history, dependencies=dependencies) env.export() return env