diff --git a/src/hatch/cli/env/prune.py b/src/hatch/cli/env/prune.py index c351741d5..2d48ecc44 100644 --- a/src/hatch/cli/env/prune.py +++ b/src/hatch/cli/env/prune.py @@ -36,6 +36,7 @@ def prune(app: Application): app.data_dir / 'env' / environment_type, app.platform, app.verbosity, + app, ) if environment.exists() or environment.build_environment_exists(): with app.status(f'Removing environment: {env_name}'): diff --git a/src/hatch/env/plugin/interface.py b/src/hatch/env/plugin/interface.py index 0b0f33a5d..375aacbb5 100644 --- a/src/hatch/env/plugin/interface.py +++ b/src/hatch/env/plugin/interface.py @@ -4,6 +4,7 @@ import sys from abc import ABC, abstractmethod from contextlib import contextmanager +from functools import cached_property from os.path import isabs from typing import TYPE_CHECKING @@ -55,7 +56,7 @@ def __init__( isolated_data_directory, platform, verbosity, - app=None, + app, ): self.__root = root self.__metadata = metadata @@ -67,24 +68,6 @@ def __init__( self.__platform = platform self.__verbosity = verbosity self.__app = app - self.__context = None - - self._system_python = None - self._env_vars = None - self._env_include = None - self._env_exclude = None - self._environment_dependencies_complex = None - self._environment_dependencies = None - self._dependencies_complex = None - self._dependencies = None - self._platforms = None - self._skip_install = None - self._dev_mode = None - self._features = None - self._description = None - self._scripts = None - self._pre_install_commands = None - self._post_install_commands = None @property def matrix_variables(self): @@ -95,19 +78,11 @@ def app(self): """ An instance of [Application](../utilities.md#hatchling.bridge.app.Application). """ - if self.__app is None: - from hatchling.bridge.app import Application - - self.__app = Application().get_safe_application() - return self.__app - @property + @cached_property def context(self): - if self.__context is None: - self.__context = self.get_context() - - return self.__context + return self.get_context() @property def verbosity(self): @@ -164,57 +139,51 @@ def config(self) -> dict: """ return self.__config - @property + @cached_property def system_python(self): - if self._system_python is None: - system_python = os.environ.get(AppEnvVars.PYTHON) - if system_python == 'self': - system_python = sys.executable - - system_python = ( - system_python - or self.platform.modules.shutil.which('python') - or self.platform.modules.shutil.which('python3') - or sys.executable - ) - if not isabs(system_python): - system_python = self.platform.modules.shutil.which(system_python) - - self._system_python = system_python - - return self._system_python - - @property + system_python = os.environ.get(AppEnvVars.PYTHON) + if system_python == 'self': + system_python = sys.executable + + system_python = ( + system_python + or self.platform.modules.shutil.which('python') + or self.platform.modules.shutil.which('python3') + or sys.executable + ) + if not isabs(system_python): + system_python = self.platform.modules.shutil.which(system_python) + + return system_python + + @cached_property def env_vars(self) -> dict: """ ```toml config-example [tool.hatch.envs..env-vars] ``` """ - if self._env_vars is None: - env_vars = self.config.get('env-vars', {}) - if not isinstance(env_vars, dict): - message = f'Field `tool.hatch.envs.{self.name}.env-vars` must be a mapping' + env_vars = self.config.get('env-vars', {}) + if not isinstance(env_vars, dict): + message = f'Field `tool.hatch.envs.{self.name}.env-vars` must be a mapping' + raise TypeError(message) + + for key, value in env_vars.items(): + if not isinstance(value, str): + message = ( + f'Environment variable `{key}` of field `tool.hatch.envs.{self.name}.env-vars` must be a string' + ) raise TypeError(message) + new_env_vars = {} + with self.metadata.context.apply_context(self.context): for key, value in env_vars.items(): - if not isinstance(value, str): - message = ( - f'Environment variable `{key}` of field `tool.hatch.envs.{self.name}.env-vars` must be a string' - ) - raise TypeError(message) + new_env_vars[key] = self.metadata.context.format(value) - new_env_vars = {} - with self.metadata.context.apply_context(self.context): - for key, value in env_vars.items(): - new_env_vars[key] = self.metadata.context.format(value) + new_env_vars[AppEnvVars.ENV_ACTIVE] = self.name + return new_env_vars - new_env_vars[AppEnvVars.ENV_ACTIVE] = self.name - self._env_vars = new_env_vars - - return self._env_vars - - @property + @cached_property def env_include(self) -> list[str]: """ ```toml config-example @@ -222,25 +191,19 @@ def env_include(self) -> list[str]: env-include = [...] ``` """ - if self._env_include is None: - env_include = self.config.get('env-include', []) - if not isinstance(env_include, list): - message = f'Field `tool.hatch.envs.{self.name}.env-include` must be an array' - raise TypeError(message) - - for i, pattern in enumerate(env_include, 1): - if not isinstance(pattern, str): - message = f'Pattern #{i} of field `tool.hatch.envs.{self.name}.env-include` must be a string' - raise TypeError(message) + env_include = self.config.get('env-include', []) + if not isinstance(env_include, list): + message = f'Field `tool.hatch.envs.{self.name}.env-include` must be an array' + raise TypeError(message) - if env_include: - self._env_include = ['HATCH_BUILD_*', *env_include] - else: - self._env_include = env_include + for i, pattern in enumerate(env_include, 1): + if not isinstance(pattern, str): + message = f'Pattern #{i} of field `tool.hatch.envs.{self.name}.env-include` must be a string' + raise TypeError(message) - return self._env_include + return ['HATCH_BUILD_*', *env_include] if env_include else env_include - @property + @cached_property def env_exclude(self) -> list[str]: """ ```toml config-example @@ -248,91 +211,77 @@ def env_exclude(self) -> list[str]: env-exclude = [...] ``` """ - if self._env_exclude is None: - env_exclude = self.config.get('env-exclude', []) - if not isinstance(env_exclude, list): - message = f'Field `tool.hatch.envs.{self.name}.env-exclude` must be an array' - raise TypeError(message) - - for i, pattern in enumerate(env_exclude, 1): - if not isinstance(pattern, str): - message = f'Pattern #{i} of field `tool.hatch.envs.{self.name}.env-exclude` must be a string' - raise TypeError(message) + env_exclude = self.config.get('env-exclude', []) + if not isinstance(env_exclude, list): + message = f'Field `tool.hatch.envs.{self.name}.env-exclude` must be an array' + raise TypeError(message) - self._env_exclude = env_exclude + for i, pattern in enumerate(env_exclude, 1): + if not isinstance(pattern, str): + message = f'Pattern #{i} of field `tool.hatch.envs.{self.name}.env-exclude` must be a string' + raise TypeError(message) - return self._env_exclude + return env_exclude - @property + @cached_property def environment_dependencies_complex(self): - if self._environment_dependencies_complex is None: - from packaging.requirements import InvalidRequirement, Requirement - - dependencies_complex = [] - with self.apply_context(): - for option in ('dependencies', 'extra-dependencies'): - dependencies = self.config.get(option, []) - if not isinstance(dependencies, list): - message = f'Field `tool.hatch.envs.{self.name}.{option}` must be an array' - raise TypeError(message) + from packaging.requirements import InvalidRequirement, Requirement - for i, entry in enumerate(dependencies, 1): - if not isinstance(entry, str): - message = ( - f'Dependency #{i} of field `tool.hatch.envs.{self.name}.{option}` must be a string' - ) - raise TypeError(message) + dependencies_complex = [] + with self.apply_context(): + for option in ('dependencies', 'extra-dependencies'): + dependencies = self.config.get(option, []) + if not isinstance(dependencies, list): + message = f'Field `tool.hatch.envs.{self.name}.{option}` must be an array' + raise TypeError(message) - try: - dependencies_complex.append(Requirement(self.metadata.context.format(entry))) - except InvalidRequirement as e: - message = f'Dependency #{i} of field `tool.hatch.envs.{self.name}.{option}` is invalid: {e}' - raise ValueError(message) from None + for i, entry in enumerate(dependencies, 1): + if not isinstance(entry, str): + message = f'Dependency #{i} of field `tool.hatch.envs.{self.name}.{option}` must be a string' + raise TypeError(message) - self._environment_dependencies_complex = dependencies_complex + try: + dependencies_complex.append(Requirement(self.metadata.context.format(entry))) + except InvalidRequirement as e: + message = f'Dependency #{i} of field `tool.hatch.envs.{self.name}.{option}` is invalid: {e}' + raise ValueError(message) from None - return self._environment_dependencies_complex + return dependencies_complex - @property + @cached_property def environment_dependencies(self) -> list[str]: """ The list of all [environment dependencies](../../config/environment/overview.md#dependencies). """ - if self._environment_dependencies is None: - self._environment_dependencies = [str(dependency) for dependency in self.environment_dependencies_complex] - - return self._environment_dependencies + return [str(dependency) for dependency in self.environment_dependencies_complex] - @property + @cached_property def dependencies_complex(self): - if self._dependencies_complex is None: - all_dependencies_complex = list(self.environment_dependencies_complex) + all_dependencies_complex = list(self.environment_dependencies_complex) - # Ensure these are checked last to speed up initial environment creation since - # they will already be installed along with the project - if (not self.skip_install and self.dev_mode) or self.features: - from hatch.utils.dep import get_project_dependencies_complex + # Ensure these are checked last to speed up initial environment creation since + # they will already be installed along with the project + if (not self.skip_install and self.dev_mode) or self.features: + from hatch.utils.dep import get_project_dependencies_complex - dependencies_complex, optional_dependencies_complex = get_project_dependencies_complex(self) + dependencies_complex, optional_dependencies_complex = get_project_dependencies_complex(self) - if not self.skip_install and self.dev_mode: - all_dependencies_complex.extend(dependencies_complex.values()) + if not self.skip_install and self.dev_mode: + all_dependencies_complex.extend(dependencies_complex.values()) - for feature in self.features: - if feature not in optional_dependencies_complex: - message = ( - f'Feature `{feature}` of field `tool.hatch.envs.{self.name}.features` is not ' - f'defined in the dynamic field `project.optional-dependencies`' - ) - raise ValueError(message) - - all_dependencies_complex.extend(optional_dependencies_complex[feature].values()) + for feature in self.features: + if feature not in optional_dependencies_complex: + message = ( + f'Feature `{feature}` of field `tool.hatch.envs.{self.name}.features` is not ' + f'defined in the dynamic field `project.optional-dependencies`' + ) + raise ValueError(message) - self._dependencies_complex = all_dependencies_complex + all_dependencies_complex.extend(optional_dependencies_complex[feature].values()) - return self._dependencies_complex + return all_dependencies_complex - @property + @cached_property def dependencies(self) -> list[str]: """ The list of all [project dependencies](../../config/metadata.md#dependencies) (if @@ -341,12 +290,9 @@ def dependencies(self) -> list[str]: [optional dependencies](../../config/environment/overview.md#features), and [environment dependencies](../../config/environment/overview.md#dependencies). """ - if self._dependencies is None: - self._dependencies = [str(dependency) for dependency in self.dependencies_complex] - - return self._dependencies + return [str(dependency) for dependency in self.dependencies_complex] - @property + @cached_property def platforms(self) -> list[str]: """ All names are stored as their lower-cased version. @@ -356,22 +302,19 @@ def platforms(self) -> list[str]: platforms = [...] ``` """ - if self._platforms is None: - platforms = self.config.get('platforms', []) - if not isinstance(platforms, list): - message = f'Field `tool.hatch.envs.{self.name}.platforms` must be an array' - raise TypeError(message) + platforms = self.config.get('platforms', []) + if not isinstance(platforms, list): + message = f'Field `tool.hatch.envs.{self.name}.platforms` must be an array' + raise TypeError(message) - for i, command in enumerate(platforms, 1): - if not isinstance(command, str): - message = f'Platform #{i} of field `tool.hatch.envs.{self.name}.platforms` must be a string' - raise TypeError(message) - - self._platforms = [platform.lower() for platform in platforms] + for i, command in enumerate(platforms, 1): + if not isinstance(command, str): + message = f'Platform #{i} of field `tool.hatch.envs.{self.name}.platforms` must be a string' + raise TypeError(message) - return self._platforms + return [platform.lower() for platform in platforms] - @property + @cached_property def skip_install(self) -> bool: """ ```toml config-example @@ -379,17 +322,14 @@ def skip_install(self) -> bool: skip-install = ... ``` """ - if self._skip_install is None: - skip_install = self.config.get('skip-install', not self.metadata.has_project_file()) - if not isinstance(skip_install, bool): - message = f'Field `tool.hatch.envs.{self.name}.skip-install` must be a boolean' - raise TypeError(message) - - self._skip_install = skip_install + skip_install = self.config.get('skip-install', not self.metadata.has_project_file()) + if not isinstance(skip_install, bool): + message = f'Field `tool.hatch.envs.{self.name}.skip-install` must be a boolean' + raise TypeError(message) - return self._skip_install + return skip_install - @property + @cached_property def dev_mode(self) -> bool: """ ```toml config-example @@ -397,58 +337,50 @@ def dev_mode(self) -> bool: dev-mode = ... ``` """ - if self._dev_mode is None: - dev_mode = self.config.get('dev-mode', True) - if not isinstance(dev_mode, bool): - message = f'Field `tool.hatch.envs.{self.name}.dev-mode` must be a boolean' - raise TypeError(message) - - self._dev_mode = dev_mode + dev_mode = self.config.get('dev-mode', True) + if not isinstance(dev_mode, bool): + message = f'Field `tool.hatch.envs.{self.name}.dev-mode` must be a boolean' + raise TypeError(message) - return self._dev_mode + return dev_mode - @property + @cached_property def features(self): - if self._features is None: - from hatchling.metadata.utils import normalize_project_name + from hatchling.metadata.utils import normalize_project_name - features = self.config.get('features', []) - if not isinstance(features, list): - message = f'Field `tool.hatch.envs.{self.name}.features` must be an array of strings' - raise TypeError(message) + features = self.config.get('features', []) + if not isinstance(features, list): + message = f'Field `tool.hatch.envs.{self.name}.features` must be an array of strings' + raise TypeError(message) - all_features = set() - for i, feature in enumerate(features, 1): - if not isinstance(feature, str): - message = f'Feature #{i} of field `tool.hatch.envs.{self.name}.features` must be a string' - raise TypeError(message) + all_features = set() + for i, feature in enumerate(features, 1): + if not isinstance(feature, str): + message = f'Feature #{i} of field `tool.hatch.envs.{self.name}.features` must be a string' + raise TypeError(message) - if not feature: - message = f'Feature #{i} of field `tool.hatch.envs.{self.name}.features` cannot be an empty string' - raise ValueError(message) + if not feature: + message = f'Feature #{i} of field `tool.hatch.envs.{self.name}.features` cannot be an empty string' + raise ValueError(message) - normalized_feature = ( - feature - if self.metadata.hatch.metadata.allow_ambiguous_features - else normalize_project_name(feature) + normalized_feature = ( + feature if self.metadata.hatch.metadata.allow_ambiguous_features else normalize_project_name(feature) + ) + if ( + not self.metadata.hatch.metadata.hook_config + and normalized_feature not in self.metadata.core.optional_dependencies + ): + message = ( + f'Feature `{normalized_feature}` of field `tool.hatch.envs.{self.name}.features` is not ' + f'defined in field `project.optional-dependencies`' ) - if ( - not self.metadata.hatch.metadata.hook_config - and normalized_feature not in self.metadata.core.optional_dependencies - ): - message = ( - f'Feature `{normalized_feature}` of field `tool.hatch.envs.{self.name}.features` is not ' - f'defined in field `project.optional-dependencies`' - ) - raise ValueError(message) + raise ValueError(message) - all_features.add(normalized_feature) + all_features.add(normalized_feature) - self._features = sorted(all_features) + return sorted(all_features) - return self._features - - @property + @cached_property def description(self) -> str: """ ```toml config-example @@ -456,105 +388,89 @@ def description(self) -> str: description = ... ``` """ - if self._description is None: - description = self.config.get('description', '') - if not isinstance(description, str): - message = f'Field `tool.hatch.envs.{self.name}.description` must be a string' - raise TypeError(message) + description = self.config.get('description', '') + if not isinstance(description, str): + message = f'Field `tool.hatch.envs.{self.name}.description` must be a string' + raise TypeError(message) - self._description = description + return description - return self._description - - @property + @cached_property def scripts(self): - if self._scripts is None: - config = {} - - # Extra scripts should come first to give less precedence - for field in ('extra-scripts', 'scripts'): - script_config = self.config.get(field, {}) - if not isinstance(script_config, dict): - message = f'Field `tool.hatch.envs.{self.name}.{field}` must be a table' - raise TypeError(message) - - for name, data in script_config.items(): - if ' ' in name: - message = ( - f'Script name `{name}` in field `tool.hatch.envs.{self.name}.{field}` ' - f'must not contain spaces' - ) - raise ValueError(message) - - commands = [] - - if isinstance(data, str): - commands.append(data) - elif isinstance(data, list): - for i, command in enumerate(data, 1): - if not isinstance(command, str): - message = ( - f'Command #{i} in field `tool.hatch.envs.{self.name}.{field}.{name}` ' - f'must be a string' - ) - raise TypeError(message) - - commands.append(command) - else: - message = ( - f'Field `tool.hatch.envs.{self.name}.{field}.{name}` must be ' - f'a string or an array of strings' - ) - raise TypeError(message) + config = {} - config[name] = commands - - seen = {} - active = [] - for script_name, commands in config.items(): - commands[:] = expand_script_commands(self.name, script_name, commands, config, seen, active) + # Extra scripts should come first to give less precedence + for field in ('extra-scripts', 'scripts'): + script_config = self.config.get(field, {}) + if not isinstance(script_config, dict): + message = f'Field `tool.hatch.envs.{self.name}.{field}` must be a table' + raise TypeError(message) - self._scripts = config + for name, data in script_config.items(): + if ' ' in name: + message = ( + f'Script name `{name}` in field `tool.hatch.envs.{self.name}.{field}` ' + f'must not contain spaces' + ) + raise ValueError(message) - return self._scripts + commands = [] - @property - def pre_install_commands(self): - if self._pre_install_commands is None: - pre_install_commands = self.config.get('pre-install-commands', []) - if not isinstance(pre_install_commands, list): - message = f'Field `tool.hatch.envs.{self.name}.pre-install-commands` must be an array' - raise TypeError(message) + if isinstance(data, str): + commands.append(data) + elif isinstance(data, list): + for i, command in enumerate(data, 1): + if not isinstance(command, str): + message = ( + f'Command #{i} in field `tool.hatch.envs.{self.name}.{field}.{name}` ' + f'must be a string' + ) + raise TypeError(message) - for i, command in enumerate(pre_install_commands, 1): - if not isinstance(command, str): + commands.append(command) + else: message = ( - f'Command #{i} of field `tool.hatch.envs.{self.name}.pre-install-commands` must be a string' + f'Field `tool.hatch.envs.{self.name}.{field}.{name}` must be ' + f'a string or an array of strings' ) raise TypeError(message) - self._pre_install_commands = list(pre_install_commands) + config[name] = commands - return self._pre_install_commands + seen = {} + active = [] + for script_name, commands in config.items(): + commands[:] = expand_script_commands(self.name, script_name, commands, config, seen, active) - @property - def post_install_commands(self): - if self._post_install_commands is None: - post_install_commands = self.config.get('post-install-commands', []) - if not isinstance(post_install_commands, list): - message = f'Field `tool.hatch.envs.{self.name}.post-install-commands` must be an array' + return config + + @cached_property + def pre_install_commands(self): + pre_install_commands = self.config.get('pre-install-commands', []) + if not isinstance(pre_install_commands, list): + message = f'Field `tool.hatch.envs.{self.name}.pre-install-commands` must be an array' + raise TypeError(message) + + for i, command in enumerate(pre_install_commands, 1): + if not isinstance(command, str): + message = f'Command #{i} of field `tool.hatch.envs.{self.name}.pre-install-commands` must be a string' raise TypeError(message) - for i, command in enumerate(post_install_commands, 1): - if not isinstance(command, str): - message = ( - f'Command #{i} of field `tool.hatch.envs.{self.name}.post-install-commands` must be a string' - ) - raise TypeError(message) + return list(pre_install_commands) - self._post_install_commands = list(post_install_commands) + @cached_property + def post_install_commands(self): + post_install_commands = self.config.get('post-install-commands', []) + if not isinstance(post_install_commands, list): + message = f'Field `tool.hatch.envs.{self.name}.post-install-commands` must be an array' + raise TypeError(message) + + for i, command in enumerate(post_install_commands, 1): + if not isinstance(command, str): + message = f'Command #{i} of field `tool.hatch.envs.{self.name}.post-install-commands` must be a string' + raise TypeError(message) - return self._post_install_commands + return list(post_install_commands) def activate(self): """ diff --git a/tests/conftest.py b/tests/conftest.py index e8fe663a5..6e14d6645 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -213,6 +213,14 @@ def compatible_python_distributions(): return tuple(get_compatible_distributions()) +@pytest.fixture(scope='session') +def global_application(): + # This is only required for the EnvironmentInterface constructor and will never be used + from hatch.cli.application import Application + + return Application(sys.exit, verbosity=0, enable_color=False, interactive=False) + + @pytest.fixture(scope='session') def devpi(tmp_path_factory, worker_id): import platform diff --git a/tests/env/plugin/test_interface.py b/tests/env/plugin/test_interface.py index dc135881c..e35f11cd1 100644 --- a/tests/env/plugin/test_interface.py +++ b/tests/env/plugin/test_interface.py @@ -36,7 +36,7 @@ def sync_dependencies(self): class TestEnvVars: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -49,11 +49,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.env_vars == environment.env_vars == {AppEnvVars.ENV_ACTIVE: 'default'} - def test_not_table(self, isolation, isolated_data_dir, platform): + def test_not_table(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-vars': 9000}}}}, @@ -69,12 +70,13 @@ def test_not_table(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.env-vars` must be a mapping'): _ = environment.env_vars - def test_value_not_string(self, isolation, isolated_data_dir, platform): + def test_value_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-vars': {'foo': 9000}}}}}, @@ -90,6 +92,7 @@ def test_value_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -97,7 +100,7 @@ def test_value_not_string(self, isolation, isolated_data_dir, platform): ): _ = environment.env_vars - def test_correct(self, isolation, isolated_data_dir, platform): + def test_correct(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-vars': {'foo': 'bar'}}}}}, @@ -113,11 +116,12 @@ def test_correct(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.env_vars == {AppEnvVars.ENV_ACTIVE: 'default', 'foo': 'bar'} - def test_context_formatting(self, isolation, isolated_data_dir, platform): + def test_context_formatting(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-vars': {'foo': '{env:FOOBAZ}-{matrix:bar}'}}}}}, @@ -133,6 +137,7 @@ def test_context_formatting(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with EnvVars({'FOOBAZ': 'baz'}): @@ -140,7 +145,7 @@ def test_context_formatting(self, isolation, isolated_data_dir, platform): class TestEnvInclude: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -153,11 +158,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.env_include == environment.env_include == [] - def test_not_array(self, isolation, isolated_data_dir, platform): + def test_not_array(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-include': 9000}}}}, @@ -173,12 +179,13 @@ def test_not_array(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.env-include` must be an array'): _ = environment.env_include - def test_pattern_not_string(self, isolation, isolated_data_dir, platform): + def test_pattern_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-include': [9000]}}}}, @@ -194,6 +201,7 @@ def test_pattern_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -201,7 +209,7 @@ def test_pattern_not_string(self, isolation, isolated_data_dir, platform): ): _ = environment.env_include - def test_correct(self, isolation, isolated_data_dir, platform): + def test_correct(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-include': ['FOO*']}}}}, @@ -217,13 +225,14 @@ def test_correct(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.env_include == ['HATCH_BUILD_*', 'FOO*'] class TestEnvExclude: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -236,11 +245,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.env_exclude == environment.env_exclude == [] - def test_not_array(self, isolation, isolated_data_dir, platform): + def test_not_array(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-exclude': 9000}}}}, @@ -256,12 +266,13 @@ def test_not_array(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.env-exclude` must be an array'): _ = environment.env_exclude - def test_pattern_not_string(self, isolation, isolated_data_dir, platform): + def test_pattern_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-exclude': [9000]}}}}, @@ -277,6 +288,7 @@ def test_pattern_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -284,7 +296,7 @@ def test_pattern_not_string(self, isolation, isolated_data_dir, platform): ): _ = environment.env_exclude - def test_correct(self, isolation, isolated_data_dir, platform): + def test_correct(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'env-exclude': ['FOO*']}}}}, @@ -300,13 +312,14 @@ def test_correct(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.env_exclude == ['FOO*'] class TestPlatforms: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -319,11 +332,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.platforms == environment.platforms == [] - def test_not_array(self, isolation, isolated_data_dir, platform): + def test_not_array(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'platforms': 9000}}}}, @@ -339,12 +353,13 @@ def test_not_array(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.platforms` must be an array'): _ = environment.platforms - def test_entry_not_string(self, isolation, isolated_data_dir, platform): + def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'platforms': [9000]}}}}, @@ -360,6 +375,7 @@ def test_entry_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -367,7 +383,7 @@ def test_entry_not_string(self, isolation, isolated_data_dir, platform): ): _ = environment.platforms - def test_correct(self, isolation, isolated_data_dir, platform): + def test_correct(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'platforms': ['macOS']}}}}, @@ -383,13 +399,14 @@ def test_correct(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.platforms == ['macos'] class TestSkipInstall: - def test_default_project(self, temp_dir, isolated_data_dir, platform): + def test_default_project(self, temp_dir, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(temp_dir, config=config) environment = MockEnvironment( @@ -402,13 +419,14 @@ def test_default_project(self, temp_dir, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) (temp_dir / 'pyproject.toml').touch() with temp_dir.as_cwd(): assert environment.skip_install is environment.skip_install is False - def test_default_no_project(self, isolation, isolated_data_dir, platform): + def test_default_no_project(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -421,11 +439,12 @@ def test_default_no_project(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.skip_install is environment.skip_install is True - def test_not_boolean(self, isolation, isolated_data_dir, platform): + def test_not_boolean(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'skip-install': 9000}}}}, @@ -441,12 +460,13 @@ def test_not_boolean(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.skip-install` must be a boolean'): _ = environment.skip_install - def test_enable(self, isolation, isolated_data_dir, platform): + def test_enable(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'skip-install': True}}}}, @@ -462,13 +482,14 @@ def test_enable(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.skip_install is True class TestDevMode: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -481,11 +502,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dev_mode is environment.dev_mode is True - def test_not_boolean(self, isolation, isolated_data_dir, platform): + def test_not_boolean(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'dev-mode': 9000}}}}, @@ -501,12 +523,13 @@ def test_not_boolean(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.dev-mode` must be a boolean'): _ = environment.dev_mode - def test_disable(self, isolation, isolated_data_dir, platform): + def test_disable(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'dev-mode': False}}}}, @@ -522,13 +545,14 @@ def test_disable(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dev_mode is False class TestFeatures: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -541,11 +565,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.features == environment.features == [] - def test_invalid_type(self, isolation, isolated_data_dir, platform): + def test_invalid_type(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'features': 9000}}}}, @@ -561,12 +586,13 @@ def test_invalid_type(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.features` must be an array of strings'): _ = environment.features - def test_correct(self, isolation, isolated_data_dir, platform): + def test_correct(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'optional-dependencies': {'foo-bar': [], 'baz': []}}, 'tool': {'hatch': {'envs': {'default': {'features': ['Foo...Bar', 'Baz', 'baZ']}}}}, @@ -582,11 +608,12 @@ def test_correct(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.features == ['baz', 'foo-bar'] - def test_feature_not_string(self, isolation, isolated_data_dir, platform): + def test_feature_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'optional-dependencies': {'foo': [], 'bar': []}}, 'tool': {'hatch': {'envs': {'default': {'features': [9000]}}}}, @@ -602,12 +629,13 @@ def test_feature_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Feature #1 of field `tool.hatch.envs.default.features` must be a string'): _ = environment.features - def test_feature_empty_string(self, isolation, isolated_data_dir, platform): + def test_feature_empty_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'optional-dependencies': {'foo': [], 'bar': []}}, 'tool': {'hatch': {'envs': {'default': {'features': ['']}}}}, @@ -623,6 +651,7 @@ def test_feature_empty_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -630,7 +659,7 @@ def test_feature_empty_string(self, isolation, isolated_data_dir, platform): ): _ = environment.features - def test_feature_undefined(self, isolation, isolated_data_dir, platform): + def test_feature_undefined(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'optional-dependencies': {'foo': []}}, 'tool': {'hatch': {'envs': {'default': {'features': ['foo', 'bar', '']}}}}, @@ -646,6 +675,7 @@ def test_feature_undefined(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -659,7 +689,7 @@ def test_feature_undefined(self, isolation, isolated_data_dir, platform): class TestDescription: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -672,11 +702,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.description == environment.description == '' - def test_not_string(self, isolation, isolated_data_dir, platform): + def test_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'description': 9000}}}}, @@ -692,12 +723,13 @@ def test_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.description` must be a string'): _ = environment.description - def test_correct(self, isolation, isolated_data_dir, platform): + def test_correct(self, isolation, isolated_data_dir, platform, global_application): description = 'foo' config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, @@ -714,13 +746,14 @@ def test_correct(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.description is description class TestDependencies: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': {'hatch': {'envs': {'default': {'skip-install': False}}}}, @@ -736,12 +769,13 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dependencies == environment.dependencies == ['dep1'] assert len(environment.dependencies) == len(environment.dependencies_complex) - def test_not_array(self, isolation, isolated_data_dir, platform): + def test_not_array(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': {'hatch': {'envs': {'default': {'dependencies': 9000}}}}, @@ -757,12 +791,13 @@ def test_not_array(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.dependencies` must be an array'): _ = environment.dependencies - def test_entry_not_string(self, isolation, isolated_data_dir, platform): + def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': {'hatch': {'envs': {'default': {'dependencies': [9000]}}}}, @@ -778,6 +813,7 @@ def test_entry_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -785,7 +821,7 @@ def test_entry_not_string(self, isolation, isolated_data_dir, platform): ): _ = environment.dependencies - def test_invalid(self, isolation, isolated_data_dir, platform): + def test_invalid(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': {'hatch': {'envs': {'default': {'dependencies': ['foo^1']}}}}, @@ -801,6 +837,7 @@ def test_invalid(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -808,7 +845,7 @@ def test_invalid(self, isolation, isolated_data_dir, platform): ): _ = environment.dependencies - def test_extra_not_array(self, isolation, isolated_data_dir, platform): + def test_extra_not_array(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': {'hatch': {'envs': {'default': {'extra-dependencies': 9000}}}}, @@ -824,12 +861,13 @@ def test_extra_not_array(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.extra-dependencies` must be an array'): _ = environment.dependencies - def test_extra_entry_not_string(self, isolation, isolated_data_dir, platform): + def test_extra_entry_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': {'hatch': {'envs': {'default': {'extra-dependencies': [9000]}}}}, @@ -845,6 +883,7 @@ def test_extra_entry_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -852,7 +891,7 @@ def test_extra_entry_not_string(self, isolation, isolated_data_dir, platform): ): _ = environment.dependencies - def test_extra_invalid(self, isolation, isolated_data_dir, platform): + def test_extra_invalid(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': {'hatch': {'envs': {'default': {'extra-dependencies': ['foo^1']}}}}, @@ -868,6 +907,7 @@ def test_extra_invalid(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -875,7 +915,7 @@ def test_extra_invalid(self, isolation, isolated_data_dir, platform): ): _ = environment.dependencies - def test_full(self, isolation, isolated_data_dir, platform): + def test_full(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': { @@ -897,11 +937,12 @@ def test_full(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dependencies == ['dep2', 'dep3', 'dep1'] - def test_context_formatting(self, isolation, isolated_data_dir, platform, uri_slash_prefix): + def test_context_formatting(self, isolation, isolated_data_dir, platform, global_application, uri_slash_prefix): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': { @@ -927,12 +968,13 @@ def test_context_formatting(self, isolation, isolated_data_dir, platform, uri_sl isolated_data_dir, platform, 0, + global_application, ) normalized_path = str(isolation).replace('\\', '/') assert environment.dependencies == ['dep2', f'proj@ file:{uri_slash_prefix}{normalized_path}', 'dep1'] - def test_full_skip_install(self, isolation, isolated_data_dir, platform): + def test_full_skip_install(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': { @@ -954,11 +996,12 @@ def test_full_skip_install(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dependencies == ['dep2', 'dep3'] - def test_full_skip_install_and_features(self, isolation, isolated_data_dir, platform): + def test_full_skip_install_and_features(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': { 'name': 'my_app', @@ -990,11 +1033,12 @@ def test_full_skip_install_and_features(self, isolation, isolated_data_dir, plat isolated_data_dir, platform, 0, + global_application, ) assert environment.dependencies == ['dep2', 'dep3', 'dep4'] - def test_full_dev_mode(self, isolation, isolated_data_dir, platform): + def test_full_dev_mode(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dependencies': ['dep1']}, 'tool': { @@ -1014,11 +1058,12 @@ def test_full_dev_mode(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dependencies == ['dep2', 'dep3'] - def test_unknown_dynamic_feature(self, helpers, temp_dir, isolated_data_dir, platform): + def test_unknown_dynamic_feature(self, helpers, temp_dir, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1', 'dynamic': ['optional-dependencies']}, 'tool': { @@ -1039,6 +1084,7 @@ def test_unknown_dynamic_feature(self, helpers, temp_dir, isolated_data_dir, pla isolated_data_dir, platform, 0, + global_application, ) build_script = temp_dir / DEFAULT_BUILD_SCRIPT @@ -1065,7 +1111,7 @@ def update(self, metadata): class TestScripts: @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_not_table(self, isolation, isolated_data_dir, platform, field): + def test_not_table(self, isolation, isolated_data_dir, platform, global_application, field): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {field: 9000}}}}, @@ -1081,13 +1127,14 @@ def test_not_table(self, isolation, isolated_data_dir, platform, field): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match=f'Field `tool.hatch.envs.default.{field}` must be a table'): _ = environment.scripts @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_name_contains_spaces(self, isolation, isolated_data_dir, platform, field): + def test_name_contains_spaces(self, isolation, isolated_data_dir, platform, global_application, field): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {field: {'foo bar': []}}}}}, @@ -1103,6 +1150,7 @@ def test_name_contains_spaces(self, isolation, isolated_data_dir, platform, fiel isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -1111,7 +1159,7 @@ def test_name_contains_spaces(self, isolation, isolated_data_dir, platform, fiel ): _ = environment.scripts - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -1124,12 +1172,13 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.scripts == environment.scripts == {} @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_single_commands(self, isolation, isolated_data_dir, platform, field): + def test_single_commands(self, isolation, isolated_data_dir, platform, global_application, field): script_config = {'foo': 'command1', 'bar': 'command2'} config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, @@ -1146,12 +1195,13 @@ def test_single_commands(self, isolation, isolated_data_dir, platform, field): isolated_data_dir, platform, 0, + global_application, ) assert environment.scripts == {'foo': ['command1'], 'bar': ['command2']} @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_multiple_commands(self, isolation, isolated_data_dir, platform, field): + def test_multiple_commands(self, isolation, isolated_data_dir, platform, global_application, field): script_config = {'foo': 'command1', 'bar': ['command3', 'command2']} config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, @@ -1168,12 +1218,13 @@ def test_multiple_commands(self, isolation, isolated_data_dir, platform, field): isolated_data_dir, platform, 0, + global_application, ) assert environment.scripts == {'foo': ['command1'], 'bar': ['command3', 'command2']} @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_multiple_commands_not_string(self, isolation, isolated_data_dir, platform, field): + def test_multiple_commands_not_string(self, isolation, isolated_data_dir, platform, global_application, field): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {field: {'foo': [9000]}}}}}, @@ -1189,6 +1240,7 @@ def test_multiple_commands_not_string(self, isolation, isolated_data_dir, platfo isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -1197,7 +1249,7 @@ def test_multiple_commands_not_string(self, isolation, isolated_data_dir, platfo _ = environment.scripts @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_config_invalid_type(self, isolation, isolated_data_dir, platform, field): + def test_config_invalid_type(self, isolation, isolated_data_dir, platform, global_application, field): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {field: {'foo': 9000}}}}}, @@ -1213,6 +1265,7 @@ def test_config_invalid_type(self, isolation, isolated_data_dir, platform, field isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -1221,7 +1274,7 @@ def test_config_invalid_type(self, isolation, isolated_data_dir, platform, field _ = environment.scripts @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_command_expansion_basic(self, isolation, isolated_data_dir, platform, field): + def test_command_expansion_basic(self, isolation, isolated_data_dir, platform, global_application, field): script_config = {'foo': 'command1', 'bar': ['command3', 'foo']} config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, @@ -1238,12 +1291,13 @@ def test_command_expansion_basic(self, isolation, isolated_data_dir, platform, f isolated_data_dir, platform, 0, + global_application, ) assert environment.scripts == {'foo': ['command1'], 'bar': ['command3', 'command1']} @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_command_expansion_multiple_nested(self, isolation, isolated_data_dir, platform, field): + def test_command_expansion_multiple_nested(self, isolation, isolated_data_dir, platform, global_application, field): script_config = { 'foo': 'command3', 'baz': ['command5', 'bar', 'foo', 'command1'], @@ -1264,6 +1318,7 @@ def test_command_expansion_multiple_nested(self, isolation, isolated_data_dir, p isolated_data_dir, platform, 0, + global_application, ) assert environment.scripts == { @@ -1273,7 +1328,9 @@ def test_command_expansion_multiple_nested(self, isolation, isolated_data_dir, p } @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_command_expansion_multiple_nested_ignore_exit_code(self, isolation, isolated_data_dir, platform, field): + def test_command_expansion_multiple_nested_ignore_exit_code( + self, isolation, isolated_data_dir, platform, global_application, field + ): script_config = { 'foo': 'command3', 'baz': ['command5', '- bar', 'foo', 'command1'], @@ -1294,6 +1351,7 @@ def test_command_expansion_multiple_nested_ignore_exit_code(self, isolation, iso isolated_data_dir, platform, 0, + global_application, ) assert environment.scripts == { @@ -1303,7 +1361,7 @@ def test_command_expansion_multiple_nested_ignore_exit_code(self, isolation, iso } @pytest.mark.parametrize('field', ['scripts', 'extra-scripts']) - def test_command_expansion_modification(self, isolation, isolated_data_dir, platform, field): + def test_command_expansion_modification(self, isolation, isolated_data_dir, platform, global_application, field): script_config = { 'foo': 'command3', 'baz': ['command5', 'bar world', 'foo', 'command1'], @@ -1324,6 +1382,7 @@ def test_command_expansion_modification(self, isolation, isolated_data_dir, plat isolated_data_dir, platform, 0, + global_application, ) assert environment.scripts == { @@ -1332,7 +1391,7 @@ def test_command_expansion_modification(self, isolation, isolated_data_dir, plat 'bar': ['command4', 'command3 hello', 'command2'], } - def test_command_expansion_circular_inheritance(self, isolation, isolated_data_dir, platform): + def test_command_expansion_circular_inheritance(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'bar', 'bar': 'foo'}}}}}, @@ -1348,6 +1407,7 @@ def test_command_expansion_circular_inheritance(self, isolation, isolated_data_d isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -1356,7 +1416,7 @@ def test_command_expansion_circular_inheritance(self, isolation, isolated_data_d ): _ = environment.scripts - def test_extra_less_precedence(self, isolation, isolated_data_dir, platform): + def test_extra_less_precedence(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': { @@ -1381,13 +1441,14 @@ def test_extra_less_precedence(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.scripts == {'foo': ['command1'], 'bar': ['command2'], 'baz': ['command3']} class TestPreInstallCommands: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -1400,11 +1461,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.pre_install_commands == environment.pre_install_commands == [] - def test_not_array(self, isolation, isolated_data_dir, platform): + def test_not_array(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'pre-install-commands': 9000}}}}, @@ -1420,12 +1482,13 @@ def test_not_array(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.pre-install-commands` must be an array'): _ = environment.pre_install_commands - def test_entry_not_string(self, isolation, isolated_data_dir, platform): + def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'pre-install-commands': [9000]}}}}, @@ -1441,6 +1504,7 @@ def test_entry_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -1448,7 +1512,7 @@ def test_entry_not_string(self, isolation, isolated_data_dir, platform): ): _ = environment.pre_install_commands - def test_correct(self, isolation, isolated_data_dir, platform): + def test_correct(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'pre-install-commands': ['baz test']}}}}, @@ -1464,13 +1528,14 @@ def test_correct(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.pre_install_commands == ['baz test'] class TestPostInstallCommands: - def test_default(self, isolation, isolated_data_dir, platform): + def test_default(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -1483,11 +1548,12 @@ def test_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.post_install_commands == environment.post_install_commands == [] - def test_not_array(self, isolation, isolated_data_dir, platform): + def test_not_array(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'post-install-commands': 9000}}}}, @@ -1503,12 +1569,13 @@ def test_not_array(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Field `tool.hatch.envs.default.post-install-commands` must be an array'): _ = environment.post_install_commands - def test_entry_not_string(self, isolation, isolated_data_dir, platform): + def test_entry_not_string(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'post-install-commands': [9000]}}}}, @@ -1524,6 +1591,7 @@ def test_entry_not_string(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises( @@ -1531,7 +1599,7 @@ def test_entry_not_string(self, isolation, isolated_data_dir, platform): ): _ = environment.post_install_commands - def test_correct(self, isolation, isolated_data_dir, platform): + def test_correct(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'post-install-commands': ['baz test']}}}}, @@ -1547,13 +1615,14 @@ def test_correct(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.post_install_commands == ['baz test'] class TestEnvVarOption: - def test_unset(self, isolation, isolated_data_dir, platform): + def test_unset(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -1566,11 +1635,12 @@ def test_unset(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.get_env_var_option('foo') == '' - def test_set(self, isolation, isolated_data_dir, platform): + def test_set(self, isolation, isolated_data_dir, platform, global_application): config = {'project': {'name': 'my_app', 'version': '0.0.1'}} project = Project(isolation, config=config) environment = MockEnvironment( @@ -1583,6 +1653,7 @@ def test_set(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with EnvVars({'HATCH_ENV_TYPE_MOCK_FOO': 'bar'}): @@ -1590,7 +1661,7 @@ def test_set(self, isolation, isolated_data_dir, platform): class TestContextFormatting: - def test_env_name(self, isolation, isolated_data_dir, platform): + def test_env_name(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command {env_name}'}}}}}, @@ -1606,11 +1677,12 @@ def test_env_name(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert list(environment.expand_command('foo')) == ['command default'] - def test_env_type(self, isolation, isolated_data_dir, platform): + def test_env_type(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command {env_type}'}}}}}, @@ -1626,11 +1698,12 @@ def test_env_type(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert list(environment.expand_command('foo')) == ['command mock'] - def test_verbosity_default(self, isolation, isolated_data_dir, platform): + def test_verbosity_default(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command -v={verbosity}'}}}}}, @@ -1646,11 +1719,12 @@ def test_verbosity_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 9000, + global_application, ) assert list(environment.expand_command('foo')) == ['command -v=9000'] - def test_verbosity_unknown_modifier(self, isolation, isolated_data_dir, platform): + def test_verbosity_unknown_modifier(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command {verbosity:bar}'}}}}}, @@ -1666,12 +1740,13 @@ def test_verbosity_unknown_modifier(self, isolation, isolated_data_dir, platform isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(ValueError, match='Unknown verbosity modifier: bar'): next(environment.expand_command('foo')) - def test_verbosity_flag_adjustment_not_integer(self, isolation, isolated_data_dir, platform): + def test_verbosity_flag_adjustment_not_integer(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command {verbosity:flag:-1.0}'}}}}}, @@ -1687,6 +1762,7 @@ def test_verbosity_flag_adjustment_not_integer(self, isolation, isolated_data_di isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(TypeError, match='Verbosity flag adjustment must be an integer: -1.0'): @@ -1706,7 +1782,9 @@ def test_verbosity_flag_adjustment_not_integer(self, isolation, isolated_data_di (9000, 'command -vvv'), ], ) - def test_verbosity_flag_default(self, isolation, isolated_data_dir, platform, verbosity, command): + def test_verbosity_flag_default( + self, isolation, isolated_data_dir, platform, global_application, verbosity, command + ): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command {verbosity:flag}'}}}}}, @@ -1722,6 +1800,7 @@ def test_verbosity_flag_default(self, isolation, isolated_data_dir, platform, ve isolated_data_dir, platform, verbosity, + global_application, ) assert list(environment.expand_command('foo')) == [command] @@ -1740,7 +1819,9 @@ def test_verbosity_flag_default(self, isolation, isolated_data_dir, platform, ve (9000, 'command -vvv'), ], ) - def test_verbosity_flag_adjustment(self, isolation, isolated_data_dir, platform, adjustment, command): + def test_verbosity_flag_adjustment( + self, isolation, isolated_data_dir, platform, global_application, adjustment, command + ): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': f'command {{verbosity:flag:{adjustment}}}'}}}}}, @@ -1756,11 +1837,12 @@ def test_verbosity_flag_adjustment(self, isolation, isolated_data_dir, platform, isolated_data_dir, platform, 0, + global_application, ) assert list(environment.expand_command('foo')) == [command] - def test_args_undefined(self, isolation, isolated_data_dir, platform): + def test_args_undefined(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command {args}'}}}}}, @@ -1776,11 +1858,12 @@ def test_args_undefined(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert list(environment.expand_command('foo')) == ['command'] - def test_args_default(self, isolation, isolated_data_dir, platform): + def test_args_default(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command {args: -bar > /dev/null}'}}}}}, @@ -1796,11 +1879,12 @@ def test_args_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert list(environment.expand_command('foo')) == ['command -bar > /dev/null'] - def test_args_default_override(self, isolation, isolated_data_dir, platform): + def test_args_default_override(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'scripts': {'foo': 'command {args: -bar > /dev/null}'}}}}}, @@ -1816,11 +1900,12 @@ def test_args_default_override(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert list(environment.expand_command('foo baz')) == ['command baz'] - def test_matrix_no_selection(self, isolation, isolated_data_dir, platform): + def test_matrix_no_selection(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'dependencies': ['pkg=={matrix}']}}}}, @@ -1836,12 +1921,13 @@ def test_matrix_no_selection(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(ValueError, match='The `matrix` context formatting field requires a modifier'): _ = environment.dependencies - def test_matrix_no_default(self, isolation, isolated_data_dir, platform): + def test_matrix_no_default(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'dependencies': ['pkg=={matrix:bar}']}}}}, @@ -1857,12 +1943,13 @@ def test_matrix_no_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) with pytest.raises(ValueError, match='Nonexistent matrix variable must set a default: bar'): _ = environment.dependencies - def test_matrix_default(self, isolation, isolated_data_dir, platform): + def test_matrix_default(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'dependencies': ['pkg=={matrix:bar:9000}']}}}}, @@ -1878,11 +1965,12 @@ def test_matrix_default(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dependencies == ['pkg==9000'] - def test_matrix_default_override(self, isolation, isolated_data_dir, platform): + def test_matrix_default_override(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': {'hatch': {'envs': {'default': {'dependencies': ['pkg=={matrix:bar:baz}']}}}}, @@ -1898,11 +1986,12 @@ def test_matrix_default_override(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dependencies == ['pkg==42'] - def test_env_vars_override(self, isolation, isolated_data_dir, platform): + def test_env_vars_override(self, isolation, isolated_data_dir, platform, global_application): config = { 'project': {'name': 'my_app', 'version': '0.0.1'}, 'tool': { @@ -1929,6 +2018,7 @@ def test_env_vars_override(self, isolation, isolated_data_dir, platform): isolated_data_dir, platform, 0, + global_application, ) assert environment.dependencies == ['pkg']