Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ cd Hatch
pip install -e .
```

Verify installation:

```bash
hatch --version
```

### Create a package template

```bash
Expand Down
8 changes: 8 additions & 0 deletions docs/articles/users/CLIReference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 6 additions & 2 deletions docs/articles/users/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions hatch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions hatch/cli_hatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,28 @@
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
from hatch_validator.package.package_service import PackageService
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:
Expand Down Expand Up @@ -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
Expand Down
122 changes: 122 additions & 0 deletions tests/test_cli_version.py
Original file line number Diff line number Diff line change
@@ -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()