diff --git a/README.md b/README.md index 000d493..18d784f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,12 @@ cd Hatch pip install -e . ``` +Verify installation: + +```bash +hatch --version +``` + ### Create a package template ```bash diff --git a/docs/articles/users/CLIReference.md b/docs/articles/users/CLIReference.md index 8e789db..d45f6d6 100644 --- a/docs/articles/users/CLIReference.md +++ b/docs/articles/users/CLIReference.md @@ -30,10 +30,18 @@ These flags are accepted by the top-level parser and apply to all commands unles | Flag | Type | Description | Default | |------|------|-------------|---------| +| `--version` | flag | Show program version and exit | n/a | | `--envs-dir` | path | Directory to store environments | `~/.hatch/envs` | | `--cache-ttl` | int | Cache time-to-live in seconds | `86400` (1 day) | | `--cache-dir` | path | Directory to store cached packages | `~/.hatch/cache` | +Example: + +```bash +hatch --version +# Output: hatch 0.6.1 +``` + ## Commands Each top-level command has its own table. Use the Syntax line before the table to see how to call it. diff --git a/docs/articles/users/GettingStarted.md b/docs/articles/users/GettingStarted.md index ea67713..509b787 100644 --- a/docs/articles/users/GettingStarted.md +++ b/docs/articles/users/GettingStarted.md @@ -105,10 +105,14 @@ pip install -e . Test that Hatch is working: ```bash -hatch --help +hatch --version ``` -You should see available commands. +You should see the installed version (e.g., `hatch 0.6.1`). You can also view available commands: + +```bash +hatch --help +``` ## First Steps diff --git a/docs/articles/users/tutorials/01-getting-started/01-installation.md b/docs/articles/users/tutorials/01-getting-started/01-installation.md index b6d091b..3ba2232 100644 --- a/docs/articles/users/tutorials/01-getting-started/01-installation.md +++ b/docs/articles/users/tutorials/01-getting-started/01-installation.md @@ -28,7 +28,13 @@ This article covers the installation of Hatch, a package manager for Model Conte pip install -e . ``` -3. Verify the installation by checking the available commands: +3. Verify the installation by checking the version: + + ```bash + hatch --version + ``` + + You should see output like `hatch 0.6.1`. You can also view available commands: ```bash hatch --help diff --git a/hatch/__init__.py b/hatch/__init__.py index 60c0aaa..e7f401b 100644 --- a/hatch/__init__.py +++ b/hatch/__init__.py @@ -5,8 +5,6 @@ and interacting with the Hatch registry. """ -__version__ = "0.4.0" - from .cli_hatch import main from .environment_manager import HatchEnvironmentManager from .package_loader import HatchPackageLoader, PackageLoaderError diff --git a/hatch/cli_hatch.py b/hatch/cli_hatch.py index 6207e9d..0146cb6 100644 --- a/hatch/cli_hatch.py +++ b/hatch/cli_hatch.py @@ -13,6 +13,7 @@ import sys from pathlib import Path from typing import Optional, List +from importlib.metadata import version, PackageNotFoundError from hatch.environment_manager import HatchEnvironmentManager from hatch_validator import HatchPackageValidator @@ -20,6 +21,20 @@ from hatch.template_generator import create_package_template from hatch.mcp_host_config import MCPHostConfigurationManager, MCPHostType, MCPHostRegistry, MCPServerConfig + +def get_hatch_version() -> str: + """Get Hatch version from package metadata. + + Returns: + str: Version string from package metadata, or 'unknown (development mode)' + if package is not installed. + """ + try: + return version('hatch') + except PackageNotFoundError: + return 'unknown (development mode)' + + def parse_host_list(host_arg: str): """Parse comma-separated host list or 'all'.""" if not host_arg: @@ -944,6 +959,14 @@ def main(): # Create argument parser parser = argparse.ArgumentParser(description="Hatch package manager CLI") + + # Add version argument + parser.add_argument( + '--version', + action='version', + version=f'%(prog)s {get_hatch_version()}' + ) + subparsers = parser.add_subparsers(dest="command", help="Command to execute") # Create template command diff --git a/tests/test_cli_version.py b/tests/test_cli_version.py new file mode 100644 index 0000000..43d4361 --- /dev/null +++ b/tests/test_cli_version.py @@ -0,0 +1,122 @@ +""" +Test suite for hatch --version command implementation. + +This module tests the version command functionality: +- Version retrieval from importlib.metadata +- Error handling for PackageNotFoundError +- CLI version display format +- Import safety after removing __version__ +- No conflicts with existing flags + +Tests follow CrackingShells testing standards using wobble framework. +""" + +import unittest +import sys +from pathlib import Path +from unittest.mock import patch, MagicMock +from io import StringIO + +# Add parent directory to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from hatch.cli_hatch import main, get_hatch_version + +try: + from wobble.decorators import regression_test, integration_test +except ImportError: + # Fallback decorators if wobble not available + def regression_test(func): + return func + + def integration_test(scope="component"): + def decorator(func): + return func + return decorator + + +class TestVersionCommand(unittest.TestCase): + """Test suite for hatch --version command implementation.""" + + @regression_test + def test_get_hatch_version_retrieves_from_metadata(self): + """Test get_hatch_version() retrieves version from importlib.metadata.""" + with patch('hatch.cli_hatch.version', return_value='0.7.0-dev.3') as mock_version: + result = get_hatch_version() + + self.assertEqual(result, '0.7.0-dev.3') + mock_version.assert_called_once_with('hatch') + + @regression_test + def test_get_hatch_version_handles_package_not_found(self): + """Test get_hatch_version() handles PackageNotFoundError gracefully.""" + from importlib.metadata import PackageNotFoundError + + with patch('hatch.cli_hatch.version', side_effect=PackageNotFoundError()): + result = get_hatch_version() + + self.assertEqual(result, 'unknown (development mode)') + + @integration_test(scope="component") + def test_version_command_displays_correct_format(self): + """Test version command displays correct format via CLI.""" + test_args = ['hatch', '--version'] + + with patch('sys.argv', test_args): + with patch('hatch.cli_hatch.get_hatch_version', return_value='0.7.0-dev.3'): + with patch('sys.stdout', new_callable=StringIO) as mock_stdout: + with self.assertRaises(SystemExit) as cm: + main() + + # argparse action='version' exits with code 0 + self.assertEqual(cm.exception.code, 0) + + # Verify output format: "hatch 0.7.0-dev.3" + output = mock_stdout.getvalue().strip() + self.assertRegex(output, r'hatch\s+0\.7\.0-dev\.3') + + @integration_test(scope="component") + def test_import_hatch_without_version_attribute(self): + """Test that importing hatch module works without __version__ attribute.""" + try: + import hatch + + # Import should succeed + self.assertIsNotNone(hatch) + + # __version__ should not exist (removed in implementation) + self.assertFalse(hasattr(hatch, '__version__'), + "hatch.__version__ should not exist after cleanup") + + except ImportError as e: + self.fail(f"Failed to import hatch module: {e}") + + @regression_test + def test_no_conflict_with_package_version_flag(self): + """Test that --version (Hatch) doesn't conflict with -v (package version).""" + # Test package add command with -v flag (package version specification) + test_args = ['hatch', 'package', 'add', 'test-package', '-v', '1.0.0'] + + with patch('sys.argv', test_args): + with patch('hatch.cli_hatch.HatchEnvironmentManager') as mock_env: + mock_env_instance = MagicMock() + mock_env.return_value = mock_env_instance + mock_env_instance.add_package_to_environment.return_value = True + + try: + main() + except SystemExit as e: + # Should execute successfully (exit code 0) + self.assertEqual(e.code, 0) + + # Verify package add was called with version argument + mock_env_instance.add_package_to_environment.assert_called_once() + call_args = mock_env_instance.add_package_to_environment.call_args + + # Version argument should be '1.0.0' + self.assertEqual(call_args[0][2], '1.0.0') # Third positional arg is version + + +if __name__ == '__main__': + unittest.main() +