Task Description
Epic: v1.0.0 Epic Final: Production Release (#75)
Acceptance Criteria: API stability guarantees, Semantic versioning implementation, Backward compatibility testing, Migration guides for breaking changes
🔗 Dependencies: Requires all v0.6.0 tasks completion (feature-complete framework)
Implementation Details
API Stability Framework
# API stability annotations and versioning
from enum import Enum
from typing import Any, Dict, List
from dataclasses import dataclass
class StabilityLevel(Enum):
EXPERIMENTAL = \"experimental\" # May change without notice
BETA = \"beta\" # Stable but may have minor changes
STABLE = \"stable\" # Guaranteed backward compatibility
DEPRECATED = \"deprecated\" # Will be removed in future version
@dataclass(frozen=True)
class APIVersion:
major: int
minor: int
patch: int
def __str__(self) -> str:
return f\"{self.major}.{self.minor}.{self.patch}\"
def is_compatible_with(self, other: 'APIVersion') -> bool:
# Major version must match for compatibility
return self.major == other.major
def api_stable(since: str):
\"\"\"Mark API as stable since given version\"\"\"
def decorator(cls_or_func):
cls_or_func.__api_stable_since__ = since
cls_or_func.__api_stability__ = StabilityLevel.STABLE
return cls_or_func
return decorator
def api_beta(since: str):
\"\"\"Mark API as beta since given version\"\"\"
def decorator(cls_or_func):
cls_or_func.__api_beta_since__ = since
cls_or_func.__api_stability__ = StabilityLevel.BETA
return cls_or_func
return decorator
def api_deprecated(since: str, removal_version: str, replacement: str = None):
\"\"\"Mark API as deprecated\"\"\"
def decorator(cls_or_func):
cls_or_func.__api_deprecated_since__ = since
cls_or_func.__api_removal_version__ = removal_version
cls_or_func.__api_replacement__ = replacement
cls_or_func.__api_stability__ = StabilityLevel.DEPRECATED
# Add runtime warning
import warnings
import functools
if hasattr(cls_or_func, '__call__'):
@functools.wraps(cls_or_func)
def wrapper(*args, **kwargs):
warnings.warn(
f\"{cls_or_func.__name__} is deprecated since v{since} \"
f\"and will be removed in v{removal_version}. \"
f\"{'Use ' + replacement + ' instead.' if replacement else ''}\",
DeprecationWarning,
stacklevel=2
)
return cls_or_func(*args, **kwargs)
return wrapper
return cls_or_func
return decorator
# Apply stability annotations to core APIs
@api_stable(since=\"1.0.0\")
class Entity(ABC, Generic[TId]):
# Stable API - no breaking changes allowed
pass
@api_stable(since=\"1.0.0\")
class Repository(ABC, Generic[TEntity, TId]):
# Stable API - backward compatibility guaranteed
pass
@api_beta(since=\"0.6.0\")
class PluginManager:
# Beta API - minor changes possible
pass
Backward Compatibility Testing
import importlib
import inspect
from typing import Set, Dict, Any
class CompatibilityChecker:
\"\"\"Check backward compatibility between versions\"\"\"
def __init__(self):
self._previous_api: Dict[str, Any] = {}
self._current_api: Dict[str, Any] = {}
def load_previous_version_api(self, version: str):
\"\"\"Load API from previous version for comparison\"\"\"
# In practice, this would load from saved API snapshots
self._previous_api = self._extract_public_api(version)
def check_compatibility(self) -> List['CompatibilityIssue']:
\"\"\"Check for compatibility issues\"\"\"
issues = []
# Check for removed classes/functions
for name, obj in self._previous_api.items():
if name not in self._current_api:
if self._is_stable_api(obj):
issues.append(CompatibilityIssue(
type=IssueType.REMOVED_STABLE_API,
name=name,
description=f\"Stable API '{name}' was removed\"
))
# Check for signature changes
for name, current_obj in self._current_api.items():
if name in self._previous_api:
previous_obj = self._previous_api[name]
if self._signature_changed(previous_obj, current_obj):
if self._is_stable_api(previous_obj):
issues.append(CompatibilityIssue(
type=IssueType.SIGNATURE_CHANGED,
name=name,
description=f\"Stable API '{name}' signature changed\"
))
return issues
def _extract_public_api(self, version: str) -> Dict[str, Any]:
\"\"\"Extract public API from a version\"\"\"
api = {}
# Scan all public classes and functions
for module_name in self._get_public_modules():
module = importlib.import_module(module_name)
for name, obj in inspect.getmembers(module):
if not name.startswith('_'): # Public API
full_name = f\"{module_name}.{name}\"
api[full_name] = obj
return api
def _is_stable_api(self, obj) -> bool:
return getattr(obj, '__api_stability__', None) == StabilityLevel.STABLE
def _signature_changed(self, old_obj, new_obj) -> bool:
if not (callable(old_obj) and callable(new_obj)):
return False
try:
old_sig = inspect.signature(old_obj)
new_sig = inspect.signature(new_obj)
return old_sig != new_sig
except (ValueError, TypeError):
return False
@dataclass
class CompatibilityIssue:
type: 'IssueType'
name: str
description: str
severity: str = \"error\"
class IssueType(Enum):
REMOVED_STABLE_API = \"removed_stable_api\"
SIGNATURE_CHANGED = \"signature_changed\"
RETURN_TYPE_CHANGED = \"return_type_changed\"
Migration Framework
class Migration(ABC):
\"\"\"Base class for migrations between versions\"\"\"
@property
@abstractmethod
def from_version(self) -> str:
pass
@property
@abstractmethod
def to_version(self) -> str:
pass
@abstractmethod
async def migrate(self, project_path: Path) -> Result[None, Exception]:
pass
@abstractmethod
def get_migration_guide(self) -> str:
\"\"\"Return human-readable migration guide\"\"\"
pass
class MigrationManager:
def __init__(self):
self._migrations: List[Migration] = []
def register_migration(self, migration: Migration):
self._migrations.append(migration)
def get_migration_path(self, from_version: str, to_version: str) -> List[Migration]:
\"\"\"Find migration path between versions\"\"\"
# Simple implementation - could use graph algorithms for complex paths
relevant_migrations = []
for migration in self._migrations:
if (migration.from_version == from_version and
migration.to_version == to_version):
relevant_migrations.append(migration)
return relevant_migrations
async def migrate_project(self, project_path: Path, from_version: str, to_version: str):
migration_path = self.get_migration_path(from_version, to_version)
for migration in migration_path:
print(f\"Applying migration: {migration.from_version} -> {migration.to_version}\")
result = await migration.migrate(project_path)
if result.is_err():
print(f\"Migration failed: {result.unwrap_err()}\")
return result
return Result.ok(None)
# Example migration
class V05ToV06Migration(Migration):
@property
def from_version(self) -> str:
return \"0.5.0\"
@property
def to_version(self) -> str:
return \"0.6.0\"
async def migrate(self, project_path: Path) -> Result[None, Exception]:
try:
# Update import statements
await self._update_imports(project_path)
# Update configuration files
await self._update_config(project_path)
return Result.ok(None)
except Exception as e:
return Result.err(e)
def get_migration_guide(self) -> str:
return \"\"\"
Migration from v0.5.0 to v0.6.0:
1. Plugin system has been introduced
2. Some import paths have changed:
- OLD: from forging_blocks.application.handlers import MessageHandler
- NEW: from forging_blocks.application.handlers.message_handler import MessageHandler
3. Configuration format updated:
- plugins.toml file added for plugin configuration
Run: fb migrate --from 0.5.0 --to 0.6.0
\"\"\"
async def _update_imports(self, project_path: Path):
# Update Python files with new import paths
for python_file in project_path.glob(\"**/*.py\"):
content = python_file.read_text()
# Replace old import patterns
content = content.replace(
\"from forging_blocks.application.handlers import MessageHandler\",
\"from forging_blocks.application.handlers.message_handler import MessageHandler\"
)
python_file.write_text(content)
Semantic Versioning Implementation
class SemanticVersion:
def __init__(self, version_string: str):
parts = version_string.split('.')
self.major = int(parts[0])
self.minor = int(parts[1])
self.patch = int(parts[2]) if len(parts) > 2 else 0
def is_breaking_change(self, new_version: 'SemanticVersion') -> bool:
return new_version.major > self.major
def is_feature_addition(self, new_version: 'SemanticVersion') -> bool:
return (self.major == new_version.major and
new_version.minor > self.minor)
def is_patch_update(self, new_version: 'SemanticVersion') -> bool:
return (self.major == new_version.major and
self.minor == new_version.minor and
new_version.patch > self.patch)
class VersionManager:
def __init__(self, current_version: str):
self.current = SemanticVersion(current_version)
def validate_api_changes(self, api_changes: List[CompatibilityIssue]) -> bool:
\"\"\"Validate that API changes match version bump\"\"\"
has_breaking_changes = any(
issue.type in [IssueType.REMOVED_STABLE_API, IssueType.SIGNATURE_CHANGED]
for issue in api_changes
)
if has_breaking_changes:
# Must be a major version bump
return self._is_major_version_bump()
return True
def _is_major_version_bump(self) -> bool:
# Check if this is a major version release
# Implementation depends on your release process
return True
Acceptance Criteria
Definition of Done
Task Description
Epic: v1.0.0 Epic Final: Production Release (#75)
Acceptance Criteria: API stability guarantees, Semantic versioning implementation, Backward compatibility testing, Migration guides for breaking changes
🔗 Dependencies: Requires all v0.6.0 tasks completion (feature-complete framework)
Implementation Details
API Stability Framework
Backward Compatibility Testing
Migration Framework
Semantic Versioning Implementation
Acceptance Criteria
Definition of Done