====================== Environment Specifiers ====================== Conda can create environments from several file formats. Currently, conda natively supports creating environments from: * A :ref:`YAML environment.yaml file ` * An :ref:`"explicit" text file ` For more information on how to manage conda environments, see the `Managing environments`_ documentation. Example plugin ============== The available readers can be extended with additional plugins via the ``conda_environment_specifiers`` hook. .. hint:: To see a fully functioning example of a Environment Spec backend, checkout the :mod:`~conda.env.specs.yaml_file` module. .. autoapiclass:: conda.plugins.types.CondaEnvironmentSpeficier :members: :undoc-members: .. autoapifunction:: conda.plugins.hookspec.CondaSpecs.conda_environment_specifiers Defining ``EnvironmentSpecBase`` -------------------------------- The first class we define is a subclass of :class:`~conda.plugins.types.EnvironmentSpecBase`. The base class is an abstract base class which requires us to define our own implementations of its abstract methods: * ``can_handle`` Determines if the defined plugin can read and operate on the provided file. * ``environment`` Expresses the provided environment file as a conda environment object. Be sure to be very specific when implementing the ``can_handle`` method. It should only return a ``True`` if the file can be parsed by the plugin. Making the ``can_handle`` method too permissive in the types of files it handles may lead to conflicts with other plugins. If multiple installed plugins are able to ``can_handle`` the same file type, conda will return an error to the user. Registering the plugin hook --------------------------- In order to make the plugin available to conda, it must be registered with the plugin manager. Define a function with the ``plugins.hookimpl`` decorator to register our plugin which returns our class wrapped in a :class:`~conda.plugins.types.CondaEnvironmentSpecifier` object. .. code-block:: python @plugins.hookimpl def conda_environment_specifiers(): yield plugins.CondaEnvSpec( name="random", environment_spec=RandomSpec, ) Using the Plugin ---------------- Once this plugin is registered, users will be able to create environments from the types of files specified by the plugin. For example to create a `random` environment using the plugin defined above: .. code-block:: bash conda env create --file /doesnt/matter/any/way.random Another example plugin ----------------------- In this example, we want to build a more realistic environemnt spec plugin. This plugin has a scheme which expresses what it expects a valid environment file to contain. In this example, a valid environment file is a ``.json`` file that defines: * an environment name (required) * a list of conda dependencies .. code-block:: python import os from pydantic import BaseModel from conda.plugins import CondaEnvironmentSpecifier, hookimpl from conda.plugins.types import EnvironmentSpecBase from conda.env.env import Environment class MySimpleEnvironment(BaseModel): """An model representing an environment file.""" # required name: str # optional conda_deps: list[str] = [] class MySimpleSpec(EnvironmentSpecBase): def __init__(self, filename=None): self.filename = filename def _parse_data(self) -> MySimpleEnvironment: """ "Validate and convert the provided file into a MySimpleEnvironment""" with open(self.filename, "rb") as fp: json_data = fp.read() return MySimpleEnvironment.model_validate_json(json_data) def can_handle(self) -> bool: """ Validates loader can process environment definition. This can handle if: * the file exists * the file can be read * the data can be parsed as JSON into a MySimpleEnvironment object :return: True if the file can be parsed and handled, False otherwise """ if not os.path.exists(self.filename): return False try: self._parse_data() except Exception: return False return True @property def environment(self) -> Environment: """Returns the Environment representation of the environment spec file""" data = self._parse_data() return Environment( name=data.name, dependencies=data.conda_deps, ) @hookimpl def conda_environment_specifiers(): yield CondaEnvironmentSpecifier( name="mysimple", environment_spec=MySimpleSpec, ) We can test this out by trying to create a conda environment with a new file that is compatible with the definied spec. Create a file ``testenv.json`` .. code-block:: { "name": "mysimpletest", "conda_deps": ["numpy", "pandas"] } Then, create the environment .. code-block:: bash $ conda env create --file testenv.json