Skip to content

Commit e8f3adf

Browse files
committed
config(feat[models,loader]): Implement modern configuration system with Pydantic models
why: The legacy configuration system had complex handling spread across multiple files with redundant validation, nested structures, and lacking formal schema. This modernization simplifies the configuration format, enhances type safety, and improves maintainability. what: - Replaced nested YAML structure with flatter, more consistent format - Implemented Pydantic v2 models for configuration (Repository, Settings, VCSPullConfig) - Created comprehensive validation logic including path normalization - Developed configuration loading functions with TypeAdapter for optimized validation - Implemented include resolution logic for configuration composition - Added consistent path handling and file resolution utilities - Created VCS interface and implementations for Git, Mercurial, and SVN - Implemented CLI commands for info and sync operations - Restructured test organization to mirror source code - Added comprehensive unit tests for models and loader - Created example configuration and API usage demonstrations - Fixed all type errors and linting issues refs: Addresses items in notes/TODO.md, specifically sections 1 (Configuration Format & Structure), 2 (Validation System), and portions of 3 (Testing System) and 4 (Internal APIs). See also: notes/proposals/01-config-format-structure.md
1 parent 74792ee commit e8f3adf

File tree

21 files changed

+1880
-1
lines changed

21 files changed

+1880
-1
lines changed

examples/api_usage.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python
2+
"""Example script demonstrating VCSPull API usage."""
3+
4+
from __future__ import annotations
5+
6+
import sys
7+
from pathlib import Path
8+
9+
# Add the parent directory to the path so we can import vcspull
10+
sys.path.insert(0, str(Path(__file__).parent.parent))
11+
12+
from vcspull import load_config
13+
from vcspull.config import resolve_includes
14+
from vcspull.vcs import get_vcs_handler
15+
16+
17+
def main() -> int:
18+
"""Main function."""
19+
# Load configuration
20+
config_path = Path(__file__).parent / "vcspull.yaml"
21+
22+
if not config_path.exists():
23+
print(f"Configuration file not found: {config_path}")
24+
return 1
25+
26+
print(f"Loading configuration from {config_path}")
27+
config = load_config(config_path)
28+
29+
# Resolve includes
30+
config = resolve_includes(config, config_path.parent)
31+
32+
# Print settings
33+
print("\nSettings:")
34+
print(f" sync_remotes: {config.settings.sync_remotes}")
35+
print(f" default_vcs: {config.settings.default_vcs}")
36+
print(f" depth: {config.settings.depth}")
37+
38+
# Print repositories
39+
print(f"\nRepositories ({len(config.repositories)}):")
40+
for repo in config.repositories:
41+
print(f" {repo.name or 'unnamed'}:")
42+
print(f" url: {repo.url}")
43+
print(f" path: {repo.path}")
44+
print(f" vcs: {repo.vcs}")
45+
if repo.rev:
46+
print(f" rev: {repo.rev}")
47+
if repo.remotes:
48+
print(f" remotes: {repo.remotes}")
49+
50+
# Example of using VCS handlers
51+
print("\nVCS Handler Example:")
52+
if config.repositories:
53+
repo = config.repositories[0]
54+
handler = get_vcs_handler(repo, config.settings.default_vcs)
55+
56+
print(f" Handler type: {type(handler).__name__}")
57+
print(f" Repository exists: {handler.exists()}")
58+
59+
# Clone the repository if it doesn't exist
60+
if not handler.exists():
61+
print(f" Cloning repository {repo.name}...")
62+
if handler.clone():
63+
print(" Clone successful")
64+
else:
65+
print(" Clone failed")
66+
67+
return 0
68+
69+
70+
if __name__ == "__main__":
71+
sys.exit(main())

examples/vcspull.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Example VCSPull configuration file
2+
3+
# Global settings
4+
settings:
5+
sync_remotes: true
6+
default_vcs: git
7+
depth: 1
8+
9+
# Repository definitions
10+
repositories:
11+
# Git repositories
12+
- name: vcspull
13+
url: https://github.com/vcs-python/vcspull.git
14+
path: ~/code/vcspull
15+
vcs: git
16+
rev: main
17+
18+
- name: libvcs
19+
url: https://github.com/vcs-python/libvcs.git
20+
path: ~/code/libvcs
21+
vcs: git
22+
remotes:
23+
upstream: https://github.com/vcs-python/libvcs.git
24+
25+
# Mercurial repository
26+
- name: mercurial-repo
27+
url: https://www.mercurial-scm.org/repo/hello
28+
path: ~/code/mercurial-hello
29+
vcs: hg
30+
31+
# Subversion repository
32+
- name: svn-repo
33+
url: https://svn.apache.org/repos/asf/subversion/trunk
34+
path: ~/code/svn-trunk
35+
vcs: svn
36+
37+
# Include other configuration files
38+
includes:
39+
- ~/more-repos.yaml

src/vcspull/README.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# VCSPull Package Structure
2+
3+
This document outlines the structure of the modernized VCSPull package.
4+
5+
## Directory Structure
6+
7+
```
8+
src/vcspull/
9+
├── __about__.py # Package metadata
10+
├── __init__.py # Package initialization
11+
├── _internal/ # Internal utilities
12+
│ ├── __init__.py
13+
│ └── logger.py # Logging utilities
14+
├── cli/ # Command-line interface
15+
│ ├── __init__.py
16+
│ └── commands.py # CLI command implementations
17+
├── config/ # Configuration handling
18+
│ ├── __init__.py
19+
│ ├── loader.py # Configuration loading functions
20+
│ └── models.py # Configuration models
21+
└── vcs/ # Version control system interfaces
22+
├── __init__.py
23+
├── base.py # Base VCS interface
24+
├── git.py # Git implementation
25+
├── mercurial.py # Mercurial implementation
26+
└── svn.py # Subversion implementation
27+
```
28+
29+
## Module Responsibilities
30+
31+
### Configuration (`config/`)
32+
33+
- **models.py**: Defines Pydantic models for configuration
34+
- **loader.py**: Provides functions for loading and resolving configuration files
35+
36+
### Version Control Systems (`vcs/`)
37+
38+
- **base.py**: Defines the abstract interface for VCS operations
39+
- **git.py**, **mercurial.py**, **svn.py**: Implementations for specific VCS types
40+
41+
### Command-line Interface (`cli/`)
42+
43+
- **commands.py**: Implements CLI commands and argument parsing
44+
45+
### Internal Utilities (`_internal/`)
46+
47+
- **logger.py**: Logging utilities for the package
48+
49+
## Configuration Format
50+
51+
VCSPull uses a YAML or JSON configuration format with the following structure:
52+
53+
```yaml
54+
settings:
55+
sync_remotes: true
56+
default_vcs: git
57+
depth: 1
58+
59+
repositories:
60+
- name: example-repo
61+
url: https://github.com/user/repo.git
62+
path: ~/code/repo
63+
vcs: git
64+
rev: main
65+
remotes:
66+
upstream: https://github.com/upstream/repo.git
67+
web_url: https://github.com/user/repo
68+
69+
includes:
70+
- ~/other-config.yaml
71+
```
72+
73+
## Usage
74+
75+
```python
76+
from vcspull import load_config
77+
78+
# Load configuration
79+
config = load_config("~/.config/vcspull/vcspull.yaml")
80+
81+
# Access repositories
82+
for repo in config.repositories:
83+
print(f"{repo.name}: {repo.url} -> {repo.path}")
84+
```
85+
86+
## Implemented Features
87+
88+
The following features have been implemented according to the modernization plan:
89+
90+
1. **Configuration Format & Structure**
91+
- Defined Pydantic v2 models for configuration
92+
- Implemented comprehensive validation logic
93+
- Created configuration loading functions
94+
- Added include resolution logic
95+
- Implemented configuration merging functions
96+
97+
2. **Validation System**
98+
- Migrated all validation to Pydantic v2 models
99+
- Used Pydantic's built-in validation capabilities
100+
- Created clear type aliases
101+
- Implemented path expansion and normalization
102+
103+
3. **Testing System**
104+
- Reorganized tests to mirror source code structure
105+
- Created separate unit test directories
106+
- Implemented test fixtures for configuration files
107+
108+
4. **Internal APIs**
109+
- Reorganized codebase according to proposed structure
110+
- Separated public and private API components
111+
- Created logical module organization
112+
- Standardized function signatures
113+
- Implemented clear parameter and return types
114+
- Added comprehensive docstrings with type information
115+
116+
5. **External APIs**
117+
- Created dedicated API module
118+
- Implemented load_config function
119+
- Defined public interfaces
120+
121+
6. **CLI System**
122+
- Implemented basic CLI commands
123+
- Added configuration handling in CLI
124+
- Created command structure
125+
126+
## Next Steps
127+
128+
The following features are planned for future implementation:
129+
130+
1. **VCS Operations**
131+
- Implement full synchronization logic
132+
- Add support for remote management
133+
- Implement revision locking
134+
135+
2. **CLI Enhancements**
136+
- Add progress reporting
137+
- Implement rich output formatting
138+
- Add repository detection command
139+
140+
3. **Documentation**
141+
- Generate JSON schema documentation
142+
- Create example configuration files
143+
- Update user documentation with new format

src/vcspull/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22
"""Manage multiple git, mercurial, svn repositories from a YAML / JSON file.
33
4-
:copyright: Copyright 2013-2018 Tony Narlock.
4+
:copyright: Copyright 2013-2024 Tony Narlock.
55
:license: MIT, see LICENSE for details
66
"""
77

@@ -12,5 +12,9 @@
1212
from logging import NullHandler
1313

1414
from . import cli
15+
from .__about__ import __version__
16+
from .config import load_config
1517

1618
logging.getLogger(__name__).addHandler(NullHandler())
19+
20+
__all__ = ["__version__", "cli", "load_config"]

src/vcspull/_internal/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""Internal utilities for VCSPull.
2+
3+
This module contains internal utilities that should not be used directly
4+
by external code.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from .logger import logger
10+
11+
__all__ = ["logger"]

src/vcspull/_internal/logger.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Logging utilities for VCSPull."""
2+
3+
from __future__ import annotations
4+
5+
import logging
6+
import sys
7+
8+
# Create a logger for this package
9+
logger = logging.getLogger("vcspull")
10+
11+
12+
def setup_logger(
13+
level: int | str = logging.INFO,
14+
log_file: str | None = None,
15+
) -> None:
16+
"""Set up the logger with handlers.
17+
18+
Parameters
19+
----------
20+
level : Union[int, str]
21+
Logging level
22+
log_file : Optional[str]
23+
Path to log file
24+
"""
25+
# Convert string level to int if needed
26+
if isinstance(level, str):
27+
level = getattr(logging, level.upper(), logging.INFO)
28+
29+
logger.setLevel(level)
30+
31+
# Remove existing handlers
32+
for handler in logger.handlers:
33+
logger.removeHandler(handler)
34+
35+
# Create console handler
36+
console_handler = logging.StreamHandler(sys.stderr)
37+
console_handler.setLevel(level)
38+
39+
# Create formatter
40+
formatter = logging.Formatter(
41+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
42+
)
43+
console_handler.setFormatter(formatter)
44+
45+
# Add console handler to logger
46+
logger.addHandler(console_handler)
47+
48+
# Add file handler if log_file is provided
49+
if log_file:
50+
file_handler = logging.FileHandler(log_file)
51+
file_handler.setLevel(level)
52+
file_handler.setFormatter(formatter)
53+
logger.addHandler(file_handler)

src/vcspull/cli/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Command-line interface for VCSPull."""
2+
3+
from __future__ import annotations
4+
5+
from .commands import cli
6+
7+
__all__ = ["cli"]

0 commit comments

Comments
 (0)