Pārlūkot izejas kodu

ruff issues fixed

xcad 5 mēneši atpakaļ
vecāks
revīzija
de2b51c86f

+ 115 - 96
archetypes/__main__.py

@@ -17,13 +17,9 @@ from rich.table import Table
 from rich.panel import Panel
 from rich.panel import Panel
 
 
 # Import CLI components
 # Import CLI components
-from cli.core.template import Template
 from cli.core.collection import VariableCollection
 from cli.core.collection import VariableCollection
 from cli.core.display import DisplayManager
 from cli.core.display import DisplayManager
 from cli.core.exceptions import (
 from cli.core.exceptions import (
-    TemplateLoadError,
-    TemplateSyntaxError,
-    TemplateValidationError,
     TemplateRenderError,
     TemplateRenderError,
 )
 )
 
 
@@ -44,7 +40,7 @@ def setup_logging(log_level: str = "WARNING") -> None:
     numeric_level = getattr(logging, log_level.upper(), None)
     numeric_level = getattr(logging, log_level.upper(), None)
     if not isinstance(numeric_level, int):
     if not isinstance(numeric_level, int):
         raise ValueError(f"Invalid log level: {log_level}")
         raise ValueError(f"Invalid log level: {log_level}")
-    
+
     logging.basicConfig(
     logging.basicConfig(
         level=numeric_level,
         level=numeric_level,
         format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
         format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
@@ -54,26 +50,30 @@ def setup_logging(log_level: str = "WARNING") -> None:
 
 
 class ArchetypeTemplate:
 class ArchetypeTemplate:
     """Simplified template for testing individual .j2 files."""
     """Simplified template for testing individual .j2 files."""
-    
+
     def __init__(self, file_path: Path, module_name: str):
     def __init__(self, file_path: Path, module_name: str):
         self.file_path = file_path
         self.file_path = file_path
         self.module_name = module_name
         self.module_name = module_name
         self.id = file_path.stem  # Filename without extension
         self.id = file_path.stem  # Filename without extension
         self.template_dir = file_path.parent
         self.template_dir = file_path.parent
-        
+
         # Create a minimal template.yaml in memory
         # Create a minimal template.yaml in memory
-        self.metadata = type('obj', (object,), {
-            'name': f"Archetype: {self.id}",
-            'description': f"Testing archetype from {file_path.name}",
-            'version': "0.1.0",
-            'author': "Testing",
-            'library': "archetype",
-            'tags': ["archetype", "test"],
-        })()
-        
+        self.metadata = type(
+            "obj",
+            (object,),
+            {
+                "name": f"Archetype: {self.id}",
+                "description": f"Testing archetype from {file_path.name}",
+                "version": "0.1.0",
+                "author": "Testing",
+                "library": "archetype",
+                "tags": ["archetype", "test"],
+            },
+        )()
+
         # Parse spec from module if available
         # Parse spec from module if available
         self.variables = self._load_module_spec()
         self.variables = self._load_module_spec()
-    
+
     def _load_module_spec(self) -> Optional[VariableCollection]:
     def _load_module_spec(self) -> Optional[VariableCollection]:
         """Load variable spec from the module and merge with extension.yaml if present."""
         """Load variable spec from the module and merge with extension.yaml if present."""
         try:
         try:
@@ -82,7 +82,7 @@ class ArchetypeTemplate:
                 from cli.modules.compose import spec
                 from cli.modules.compose import spec
                 from collections import OrderedDict
                 from collections import OrderedDict
                 import yaml
                 import yaml
-                
+
                 # Convert spec to dict if needed
                 # Convert spec to dict if needed
                 if isinstance(spec, (dict, OrderedDict)):
                 if isinstance(spec, (dict, OrderedDict)):
                     spec_dict = OrderedDict(spec)
                     spec_dict = OrderedDict(spec)
@@ -90,63 +90,72 @@ class ArchetypeTemplate:
                     # Extract dict from existing VariableCollection (shouldn't happen)
                     # Extract dict from existing VariableCollection (shouldn't happen)
                     spec_dict = OrderedDict()
                     spec_dict = OrderedDict()
                 else:
                 else:
-                    logging.warning(f"Spec for {self.module_name} has unexpected type: {type(spec)}")
+                    logging.warning(
+                        f"Spec for {self.module_name} has unexpected type: {type(spec)}"
+                    )
                     return None
                     return None
-                
+
                 # Check for extension.yaml in the archetype directory
                 # Check for extension.yaml in the archetype directory
                 extension_file = self.template_dir / "extension.yaml"
                 extension_file = self.template_dir / "extension.yaml"
                 if extension_file.exists():
                 if extension_file.exists():
                     try:
                     try:
-                        with open(extension_file, 'r') as f:
+                        with open(extension_file, "r") as f:
                             extension_vars = yaml.safe_load(f)
                             extension_vars = yaml.safe_load(f)
-                        
+
                         if extension_vars:
                         if extension_vars:
                             # Apply extension defaults to existing variables in their sections
                             # Apply extension defaults to existing variables in their sections
                             # Extension vars that don't exist will be added to a "testing" section
                             # Extension vars that don't exist will be added to a "testing" section
                             applied_count = 0
                             applied_count = 0
                             new_vars = {}
                             new_vars = {}
-                            
+
                             for var_name, var_spec in extension_vars.items():
                             for var_name, var_spec in extension_vars.items():
                                 found = False
                                 found = False
                                 # Search for the variable in existing sections
                                 # Search for the variable in existing sections
                                 for section_name, section_data in spec_dict.items():
                                 for section_name, section_data in spec_dict.items():
-                                    if "vars" in section_data and var_name in section_data["vars"]:
+                                    if (
+                                        "vars" in section_data
+                                        and var_name in section_data["vars"]
+                                    ):
                                         # Update the default value for existing variable
                                         # Update the default value for existing variable
                                         if "default" in var_spec:
                                         if "default" in var_spec:
-                                            section_data["vars"][var_name]["default"] = var_spec["default"]
+                                            section_data["vars"][var_name][
+                                                "default"
+                                            ] = var_spec["default"]
                                             applied_count += 1
                                             applied_count += 1
                                             found = True
                                             found = True
                                             break
                                             break
-                                
+
                                 # If variable doesn't exist in spec, add it to testing section
                                 # If variable doesn't exist in spec, add it to testing section
                                 if not found:
                                 if not found:
                                     new_vars[var_name] = var_spec
                                     new_vars[var_name] = var_spec
-                            
+
                             # Add new test-only variables to testing section
                             # Add new test-only variables to testing section
                             if new_vars:
                             if new_vars:
                                 if "testing" not in spec_dict:
                                 if "testing" not in spec_dict:
                                     spec_dict["testing"] = {
                                     spec_dict["testing"] = {
                                         "title": "Testing Variables",
                                         "title": "Testing Variables",
                                         "description": "Additional variables for archetype testing",
                                         "description": "Additional variables for archetype testing",
-                                        "vars": {}
+                                        "vars": {},
                                     }
                                     }
                                 spec_dict["testing"]["vars"].update(new_vars)
                                 spec_dict["testing"]["vars"].update(new_vars)
-                            
-                            logging.debug(f"Applied {applied_count} extension defaults, added {len(new_vars)} new test variables from {extension_file}")
+
+                            logging.debug(
+                                f"Applied {applied_count} extension defaults, added {len(new_vars)} new test variables from {extension_file}"
+                            )
                     except Exception as e:
                     except Exception as e:
                         logging.warning(f"Failed to load extension.yaml: {e}")
                         logging.warning(f"Failed to load extension.yaml: {e}")
-                
+
                 return VariableCollection(spec_dict)
                 return VariableCollection(spec_dict)
         except Exception as e:
         except Exception as e:
             logging.warning(f"Could not load spec for module {self.module_name}: {e}")
             logging.warning(f"Could not load spec for module {self.module_name}: {e}")
             return None
             return None
-    
+
     def render(self, variables: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
     def render(self, variables: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
         """Render the single .j2 file using CLI's Template class."""
         """Render the single .j2 file using CLI's Template class."""
         # Create a minimal template directory structure in memory
         # Create a minimal template directory structure in memory
         # by using the Template class's rendering capabilities
         # by using the Template class's rendering capabilities
         from jinja2 import Environment, FileSystemLoader, StrictUndefined
         from jinja2 import Environment, FileSystemLoader, StrictUndefined
-        
+
         # Set up Jinja2 environment with the archetype directory
         # Set up Jinja2 environment with the archetype directory
         env = Environment(
         env = Environment(
             loader=FileSystemLoader(str(self.template_dir)),
             loader=FileSystemLoader(str(self.template_dir)),
@@ -155,11 +164,11 @@ class ArchetypeTemplate:
             lstrip_blocks=True,
             lstrip_blocks=True,
             keep_trailing_newline=True,
             keep_trailing_newline=True,
         )
         )
-        
+
         # Get variable values
         # Get variable values
         if variables is None:
         if variables is None:
             variables = {}
             variables = {}
-        
+
         # Get default values from spec if available
         # Get default values from spec if available
         if self.variables:
         if self.variables:
             # Get ALL variable values, not just satisfied ones
             # Get ALL variable values, not just satisfied ones
@@ -175,15 +184,15 @@ class ArchetypeTemplate:
             final_values = {**spec_values, **variables}
             final_values = {**spec_values, **variables}
         else:
         else:
             final_values = variables
             final_values = variables
-        
+
         try:
         try:
             # Load and render the template
             # Load and render the template
             template = env.get_template(self.file_path.name)
             template = env.get_template(self.file_path.name)
             rendered_content = template.render(**final_values)
             rendered_content = template.render(**final_values)
-            
+
             # Remove .j2 extension for output filename
             # Remove .j2 extension for output filename
-            output_filename = self.file_path.name.replace('.j2', '')
-            
+            output_filename = self.file_path.name.replace(".j2", "")
+
             return {output_filename: rendered_content}
             return {output_filename: rendered_content}
         except Exception as e:
         except Exception as e:
             raise TemplateRenderError(f"Failed to render {self.file_path.name}: {e}")
             raise TemplateRenderError(f"Failed to render {self.file_path.name}: {e}")
@@ -192,11 +201,11 @@ class ArchetypeTemplate:
 def find_archetypes(module_name: str) -> List[Path]:
 def find_archetypes(module_name: str) -> List[Path]:
     """Find all .j2 files in the module's archetype directory."""
     """Find all .j2 files in the module's archetype directory."""
     module_dir = ARCHETYPES_DIR / module_name
     module_dir = ARCHETYPES_DIR / module_name
-    
+
     if not module_dir.exists():
     if not module_dir.exists():
         console.print(f"[red]Module directory not found: {module_dir}[/red]")
         console.print(f"[red]Module directory not found: {module_dir}[/red]")
         return []
         return []
-    
+
     # Find all .j2 files
     # Find all .j2 files
     j2_files = list(module_dir.glob("*.j2"))
     j2_files = list(module_dir.glob("*.j2"))
     return sorted(j2_files)
     return sorted(j2_files)
@@ -205,98 +214,107 @@ def find_archetypes(module_name: str) -> List[Path]:
 def create_module_commands(module_name: str) -> Typer:
 def create_module_commands(module_name: str) -> Typer:
     """Create a Typer app with commands for a specific module."""
     """Create a Typer app with commands for a specific module."""
     module_app = Typer(help=f"Manage {module_name} archetypes")
     module_app = Typer(help=f"Manage {module_name} archetypes")
-    
+
     @module_app.command()
     @module_app.command()
     def list() -> None:
     def list() -> None:
         """List all archetype files for this module."""
         """List all archetype files for this module."""
         archetypes = find_archetypes(module_name)
         archetypes = find_archetypes(module_name)
-        
+
         if not archetypes:
         if not archetypes:
             display.display_warning(
             display.display_warning(
                 f"No archetypes found for module '{module_name}'",
                 f"No archetypes found for module '{module_name}'",
-                context=f"directory: {ARCHETYPES_DIR / module_name}"
+                context=f"directory: {ARCHETYPES_DIR / module_name}",
             )
             )
             return
             return
-        
+
         # Create table
         # Create table
-        table = Table(title=f"Archetypes for '{module_name}'", show_header=True, header_style="bold cyan")
+        table = Table(
+            title=f"Archetypes for '{module_name}'",
+            show_header=True,
+            header_style="bold cyan",
+        )
         table.add_column("ID", style="cyan")
         table.add_column("ID", style="cyan")
         table.add_column("Filename", style="white")
         table.add_column("Filename", style="white")
         table.add_column("Size", style="dim")
         table.add_column("Size", style="dim")
-        
+
         for archetype_path in archetypes:
         for archetype_path in archetypes:
             file_size = archetype_path.stat().st_size
             file_size = archetype_path.stat().st_size
             if file_size < 1024:
             if file_size < 1024:
                 size_str = f"{file_size}B"
                 size_str = f"{file_size}B"
             else:
             else:
                 size_str = f"{file_size / 1024:.1f}KB"
                 size_str = f"{file_size / 1024:.1f}KB"
-            
+
             table.add_row(
             table.add_row(
                 archetype_path.stem,
                 archetype_path.stem,
                 archetype_path.name,
                 archetype_path.name,
                 size_str,
                 size_str,
             )
             )
-        
+
         console.print(table)
         console.print(table)
         console.print(f"\n[dim]Found {len(archetypes)} archetype(s)[/dim]")
         console.print(f"\n[dim]Found {len(archetypes)} archetype(s)[/dim]")
-    
+
     @module_app.command()
     @module_app.command()
     def show(
     def show(
         id: str = Argument(..., help="Archetype ID (filename without .j2)"),
         id: str = Argument(..., help="Archetype ID (filename without .j2)"),
     ) -> None:
     ) -> None:
         """Show details of an archetype file."""
         """Show details of an archetype file."""
         archetypes = find_archetypes(module_name)
         archetypes = find_archetypes(module_name)
-        
+
         # Find the archetype
         # Find the archetype
         archetype_path = None
         archetype_path = None
         for path in archetypes:
         for path in archetypes:
             if path.stem == id:
             if path.stem == id:
                 archetype_path = path
                 archetype_path = path
                 break
                 break
-        
+
         if not archetype_path:
         if not archetype_path:
             display.display_error(
             display.display_error(
-                f"Archetype '{id}' not found",
-                context=f"module '{module_name}'"
+                f"Archetype '{id}' not found", context=f"module '{module_name}'"
             )
             )
             return
             return
-        
+
         # Load archetype
         # Load archetype
         archetype = ArchetypeTemplate(archetype_path, module_name)
         archetype = ArchetypeTemplate(archetype_path, module_name)
-        
+
         # Display details
         # Display details
         console.print()
         console.print()
-        console.print(Panel(
-            f"[bold]{archetype.metadata.name}[/bold]\n"
-            f"{archetype.metadata.description}\n\n"
-            f"[dim]Module:[/dim] {module_name}\n"
-            f"[dim]File:[/dim] {archetype_path.name}\n"
-            f"[dim]Path:[/dim] {archetype_path}",
-            title="Archetype Details",
-            border_style="cyan",
-        ))
-        
+        console.print(
+            Panel(
+                f"[bold]{archetype.metadata.name}[/bold]\n"
+                f"{archetype.metadata.description}\n\n"
+                f"[dim]Module:[/dim] {module_name}\n"
+                f"[dim]File:[/dim] {archetype_path.name}\n"
+                f"[dim]Path:[/dim] {archetype_path}",
+                title="Archetype Details",
+                border_style="cyan",
+            )
+        )
+
         # Show variables if spec is loaded
         # Show variables if spec is loaded
         if archetype.variables:
         if archetype.variables:
             console.print("\n[bold]Available Variables:[/bold]")
             console.print("\n[bold]Available Variables:[/bold]")
-            
+
             # Access the private _sections attribute
             # Access the private _sections attribute
             for section_name, section in archetype.variables._sections.items():
             for section_name, section in archetype.variables._sections.items():
                 if section.variables:
                 if section.variables:
-                    console.print(f"\n[cyan]{section.title or section_name.capitalize()}:[/cyan]")
+                    console.print(
+                        f"\n[cyan]{section.title or section_name.capitalize()}:[/cyan]"
+                    )
                     for var_name, var in section.variables.items():
                     for var_name, var in section.variables.items():
-                        default = var.value if var.value is not None else "[dim]none[/dim]"
+                        default = (
+                            var.value if var.value is not None else "[dim]none[/dim]"
+                        )
                         console.print(f"  {var_name}: {default}")
                         console.print(f"  {var_name}: {default}")
         else:
         else:
             console.print("\n[yellow]No variable spec loaded for this module[/yellow]")
             console.print("\n[yellow]No variable spec loaded for this module[/yellow]")
-        
+
         # Show file content
         # Show file content
         console.print("\n[bold]Template Content:[/bold]")
         console.print("\n[bold]Template Content:[/bold]")
         console.print("─" * 80)
         console.print("─" * 80)
-        with open(archetype_path, 'r') as f:
+        with open(archetype_path, "r") as f:
             console.print(f.read())
             console.print(f.read())
         console.print()
         console.print()
-    
+
     @module_app.command()
     @module_app.command()
     def generate(
     def generate(
         id: str = Argument(..., help="Archetype ID (filename without .j2)"),
         id: str = Argument(..., help="Archetype ID (filename without .j2)"),
@@ -313,28 +331,25 @@ def create_module_commands(module_name: str) -> Typer:
         """Generate output from an archetype file (always in preview mode)."""
         """Generate output from an archetype file (always in preview mode)."""
         # Archetypes ALWAYS run in dry-run mode with content display
         # Archetypes ALWAYS run in dry-run mode with content display
         # This is a testing tool - it never writes actual files
         # This is a testing tool - it never writes actual files
-        dry_run = True
-        show_content = True
-        
+
         archetypes = find_archetypes(module_name)
         archetypes = find_archetypes(module_name)
-        
+
         # Find the archetype
         # Find the archetype
         archetype_path = None
         archetype_path = None
         for path in archetypes:
         for path in archetypes:
             if path.stem == id:
             if path.stem == id:
                 archetype_path = path
                 archetype_path = path
                 break
                 break
-        
+
         if not archetype_path:
         if not archetype_path:
             display.display_error(
             display.display_error(
-                f"Archetype '{id}' not found",
-                context=f"module '{module_name}'"
+                f"Archetype '{id}' not found", context=f"module '{module_name}'"
             )
             )
             return
             return
-        
+
         # Load archetype
         # Load archetype
         archetype = ArchetypeTemplate(archetype_path, module_name)
         archetype = ArchetypeTemplate(archetype_path, module_name)
-        
+
         # Parse variable overrides
         # Parse variable overrides
         variables = {}
         variables = {}
         if var:
         if var:
@@ -343,48 +358,51 @@ def create_module_commands(module_name: str) -> Typer:
                     key, value = var_option.split("=", 1)
                     key, value = var_option.split("=", 1)
                     variables[key] = value
                     variables[key] = value
                 else:
                 else:
-                    console.print(f"[yellow]Warning: Invalid --var format '{var_option}' (use KEY=VALUE)[/yellow]")
-        
+                    console.print(
+                        f"[yellow]Warning: Invalid --var format '{var_option}' (use KEY=VALUE)[/yellow]"
+                    )
+
         # Render the archetype
         # Render the archetype
         try:
         try:
             rendered_files = archetype.render(variables)
             rendered_files = archetype.render(variables)
         except Exception as e:
         except Exception as e:
             display.display_error(
             display.display_error(
-                f"Failed to render archetype: {e}",
-                context=f"archetype '{id}'"
+                f"Failed to render archetype: {e}", context=f"archetype '{id}'"
             )
             )
             return
             return
-        
+
         # Determine output directory (for display purposes only)
         # Determine output directory (for display purposes only)
         if directory:
         if directory:
             output_dir = Path(directory)
             output_dir = Path(directory)
         else:
         else:
             output_dir = Path.cwd()
             output_dir = Path.cwd()
-        
+
         # Always show preview (archetypes never write files)
         # Always show preview (archetypes never write files)
         console.print()
         console.print()
         console.print("[bold cyan]Archetype Preview (Testing Mode)[/bold cyan]")
         console.print("[bold cyan]Archetype Preview (Testing Mode)[/bold cyan]")
-        console.print("[dim]This tool never writes files - it's for testing template snippets only[/dim]")
+        console.print(
+            "[dim]This tool never writes files - it's for testing template snippets only[/dim]"
+        )
         console.print()
         console.print()
         console.print(f"[dim]Reference directory:[/dim] {output_dir}")
         console.print(f"[dim]Reference directory:[/dim] {output_dir}")
         console.print(f"[dim]Files to preview:[/dim] {len(rendered_files)}")
         console.print(f"[dim]Files to preview:[/dim] {len(rendered_files)}")
         console.print()
         console.print()
-        
+
         for filename, content in rendered_files.items():
         for filename, content in rendered_files.items():
             full_path = output_dir / filename
             full_path = output_dir / filename
             status = "Would overwrite" if full_path.exists() else "Would create"
             status = "Would overwrite" if full_path.exists() else "Would create"
-            size = len(content.encode('utf-8'))
+            size = len(content.encode("utf-8"))
             console.print(f"  [{status}] {filename} ({size} bytes)")
             console.print(f"  [{status}] {filename} ({size} bytes)")
-        
+
         console.print()
         console.print()
         console.print("[bold]Rendered Content:[/bold]")
         console.print("[bold]Rendered Content:[/bold]")
         console.print("─" * 80)
         console.print("─" * 80)
         for filename, content in rendered_files.items():
         for filename, content in rendered_files.items():
             console.print(content)
             console.print(content)
-        
+
         console.print()
         console.print()
         display.display_success("Preview complete - no files were written")
         display.display_success("Preview complete - no files were written")
-    
+
     return module_app
     return module_app
 
 
 
 
@@ -393,7 +411,7 @@ def init_app() -> None:
     # Find all module directories in archetypes/
     # Find all module directories in archetypes/
     if ARCHETYPES_DIR.exists():
     if ARCHETYPES_DIR.exists():
         for module_dir in ARCHETYPES_DIR.iterdir():
         for module_dir in ARCHETYPES_DIR.iterdir():
-            if module_dir.is_dir() and not module_dir.name.startswith(('_', '.')):
+            if module_dir.is_dir() and not module_dir.name.startswith(("_", ".")):
                 module_name = module_dir.name
                 module_name = module_dir.name
                 # Register module commands
                 # Register module commands
                 module_app = create_module_commands(module_name)
                 module_app = create_module_commands(module_name)
@@ -413,10 +431,11 @@ def main(
         setup_logging(log_level)
         setup_logging(log_level)
     else:
     else:
         logging.disable(logging.CRITICAL)
         logging.disable(logging.CRITICAL)
-    
+
     import click
     import click
+
     ctx = click.get_current_context()
     ctx = click.get_current_context()
-    
+
     if ctx.invoked_subcommand is None:
     if ctx.invoked_subcommand is None:
         console.print(ctx.get_help())
         console.print(ctx.get_help())
         sys.exit(0)
         sys.exit(0)

+ 26 - 20
cli/core/collection.py

@@ -71,7 +71,7 @@ class VariableCollection:
             "key": key,
             "key": key,
             "title": data.get("title", key.replace("_", " ").title()),
             "title": data.get("title", key.replace("_", " ").title()),
         }
         }
-        
+
         # Only add optional fields if explicitly provided in the source data
         # Only add optional fields if explicitly provided in the source data
         if "description" in data:
         if "description" in data:
             section_init_data["description"] = data["description"]
             section_init_data["description"] = data["description"]
@@ -83,7 +83,7 @@ class VariableCollection:
             section_init_data["required"] = True
             section_init_data["required"] = True
         if "needs" in data:
         if "needs" in data:
             section_init_data["needs"] = data["needs"]
             section_init_data["needs"] = data["needs"]
-            
+
         return VariableSection(section_init_data)
         return VariableSection(section_init_data)
 
 
     def _initialize_variables(
     def _initialize_variables(
@@ -402,31 +402,31 @@ class VariableCollection:
 
 
     def reset_disabled_bool_variables(self) -> list[str]:
     def reset_disabled_bool_variables(self) -> list[str]:
         """Reset bool variables with unsatisfied dependencies to False.
         """Reset bool variables with unsatisfied dependencies to False.
-        
+
         This ensures that disabled bool variables don't accidentally remain True
         This ensures that disabled bool variables don't accidentally remain True
         and cause confusion in templates or configuration.
         and cause confusion in templates or configuration.
-        
+
         Note: CLI-provided variables are NOT reset here - they are validated
         Note: CLI-provided variables are NOT reset here - they are validated
         later in validate_all() to provide better error messages.
         later in validate_all() to provide better error messages.
-        
+
         Returns:
         Returns:
             List of variable names that were reset
             List of variable names that were reset
         """
         """
         reset_vars = []
         reset_vars = []
-        
+
         for section_key, section in self._sections.items():
         for section_key, section in self._sections.items():
             # Check if section dependencies are satisfied
             # Check if section dependencies are satisfied
             section_satisfied = self.is_section_satisfied(section_key)
             section_satisfied = self.is_section_satisfied(section_key)
             is_enabled = section.is_enabled()
             is_enabled = section.is_enabled()
-            
+
             for var_name, variable in section.variables.items():
             for var_name, variable in section.variables.items():
                 # Only process bool variables
                 # Only process bool variables
                 if variable.type != "bool":
                 if variable.type != "bool":
                     continue
                     continue
-                    
+
                 # Check if variable's own dependencies are satisfied
                 # Check if variable's own dependencies are satisfied
                 var_satisfied = self.is_variable_satisfied(var_name)
                 var_satisfied = self.is_variable_satisfied(var_name)
-                
+
                 # If section is disabled OR variable dependencies aren't met, reset to False
                 # If section is disabled OR variable dependencies aren't met, reset to False
                 if not section_satisfied or not is_enabled or not var_satisfied:
                 if not section_satisfied or not is_enabled or not var_satisfied:
                     # Only reset if current value is not already False
                     # Only reset if current value is not already False
@@ -434,11 +434,11 @@ class VariableCollection:
                         # Don't reset CLI-provided variables - they'll be validated later
                         # Don't reset CLI-provided variables - they'll be validated later
                         if variable.origin == "cli":
                         if variable.origin == "cli":
                             continue
                             continue
-                        
+
                         # Store original value if not already stored (for display purposes)
                         # Store original value if not already stored (for display purposes)
                         if not hasattr(variable, "_original_disabled"):
                         if not hasattr(variable, "_original_disabled"):
                             variable._original_disabled = variable.value
                             variable._original_disabled = variable.value
-                        
+
                         variable.value = False
                         variable.value = False
                         reset_vars.append(var_name)
                         reset_vars.append(var_name)
                         logger.debug(
                         logger.debug(
@@ -446,7 +446,7 @@ class VariableCollection:
                             f"(section satisfied: {section_satisfied}, enabled: {is_enabled}, "
                             f"(section satisfied: {section_satisfied}, enabled: {is_enabled}, "
                             f"var satisfied: {var_satisfied})"
                             f"var satisfied: {var_satisfied})"
                         )
                         )
-        
+
         return reset_vars
         return reset_vars
 
 
     def sort_sections(self) -> None:
     def sort_sections(self) -> None:
@@ -691,12 +691,16 @@ class VariableCollection:
         for section_key, section in self._sections.items():
         for section_key, section in self._sections.items():
             section_satisfied = self.is_section_satisfied(section_key)
             section_satisfied = self.is_section_satisfied(section_key)
             is_enabled = section.is_enabled()
             is_enabled = section.is_enabled()
-            
+
             for var_name, variable in section.variables.items():
             for var_name, variable in section.variables.items():
                 # Check CLI-provided bool variables with unsatisfied dependencies
                 # Check CLI-provided bool variables with unsatisfied dependencies
-                if variable.type == "bool" and variable.origin == "cli" and variable.value is not False:
+                if (
+                    variable.type == "bool"
+                    and variable.origin == "cli"
+                    and variable.value is not False
+                ):
                     var_satisfied = self.is_variable_satisfied(var_name)
                     var_satisfied = self.is_variable_satisfied(var_name)
-                    
+
                     if not section_satisfied or not is_enabled or not var_satisfied:
                     if not section_satisfied or not is_enabled or not var_satisfied:
                         # Build error message with unmet needs (use set to avoid duplicates)
                         # Build error message with unmet needs (use set to avoid duplicates)
                         unmet_needs = set()
                         unmet_needs = set()
@@ -708,8 +712,12 @@ class VariableCollection:
                             for need in variable.needs:
                             for need in variable.needs:
                                 if not self._is_need_satisfied(need):
                                 if not self._is_need_satisfied(need):
                                     unmet_needs.add(need)
                                     unmet_needs.add(need)
-                        
-                        needs_str = ", ".join(sorted(unmet_needs)) if unmet_needs else "dependencies not satisfied"
+
+                        needs_str = (
+                            ", ".join(sorted(unmet_needs))
+                            if unmet_needs
+                            else "dependencies not satisfied"
+                        )
                         errors.append(
                         errors.append(
                             f"{section.key}.{var_name} (set via CLI to {variable.value} but requires: {needs_str})"
                             f"{section.key}.{var_name} (set via CLI to {variable.value} but requires: {needs_str})"
                         )
                         )
@@ -893,9 +901,7 @@ class VariableCollection:
 
 
                 # Special handling for needs (allow explicit null/empty to clear)
                 # Special handling for needs (allow explicit null/empty to clear)
                 if "needs" in other_var._explicit_fields:
                 if "needs" in other_var._explicit_fields:
-                    update["needs"] = (
-                        other_var.needs.copy() if other_var.needs else []
-                    )
+                    update["needs"] = other_var.needs.copy() if other_var.needs else []
 
 
                 # Special handling for value/default (allow explicit null to clear)
                 # Special handling for value/default (allow explicit null to clear)
                 if "value" in other_var._explicit_fields:
                 if "value" in other_var._explicit_fields:

+ 14 - 9
cli/core/display.py

@@ -205,7 +205,11 @@ class DisplayManager:
             version = (
             version = (
                 str(template.metadata.version) if template.metadata.version else ""
                 str(template.metadata.version) if template.metadata.version else ""
             )
             )
-            schema = template.schema_version if hasattr(template, 'schema_version') else "1.0"
+            schema = (
+                template.schema_version
+                if hasattr(template, "schema_version")
+                else "1.0"
+            )
 
 
             # Show library with type indicator and color
             # Show library with type indicator and color
             library_name = template.metadata.library or ""
             library_name = template.metadata.library or ""
@@ -229,9 +233,7 @@ class DisplayManager:
 
 
         console.print(table)
         console.print(table)
 
 
-    def display_template_details(
-        self, template: Template, template_id: str
-    ) -> None:
+    def display_template_details(self, template: Template, template_id: str) -> None:
         """Display template information panel and variables table.
         """Display template information panel and variables table.
 
 
         Args:
         Args:
@@ -371,7 +373,9 @@ class DisplayManager:
             if template.metadata.version
             if template.metadata.version
             else "Not specified"
             else "Not specified"
         )
         )
-        schema = template.schema_version if hasattr(template, 'schema_version') else "1.0"
+        schema = (
+            template.schema_version if hasattr(template, "schema_version") else "1.0"
+        )
         description = template.metadata.description or "No description available"
         description = template.metadata.description or "No description available"
 
 
         # Get library information
         # Get library information
@@ -456,9 +460,7 @@ class DisplayManager:
         if file_tree.children:
         if file_tree.children:
             console.print(file_tree)
             console.print(file_tree)
 
 
-    def _display_variables_table(
-        self, template: Template
-    ) -> None:
+    def _display_variables_table(self, template: Template) -> None:
         """Display a table of variables for a template.
         """Display a table of variables for a template.
 
 
         All variables and sections are always shown. Disabled sections/variables
         All variables and sections are always shown. Disabled sections/variables
@@ -531,7 +533,10 @@ class DisplayManager:
                 # Special case: disabled bool variables show as "original → False"
                 # Special case: disabled bool variables show as "original → False"
                 if (is_dimmed or not var_satisfied) and variable.type == "bool":
                 if (is_dimmed or not var_satisfied) and variable.type == "bool":
                     # Show that disabled bool variables are forced to False
                     # Show that disabled bool variables are forced to False
-                    if hasattr(variable, "_original_disabled") and variable._original_disabled is not False:
+                    if (
+                        hasattr(variable, "_original_disabled")
+                        and variable._original_disabled is not False
+                    ):
                         orig_val = str(variable._original_disabled)
                         orig_val = str(variable._original_disabled)
                         default_val = f"{orig_val} {IconManager.arrow_right()} False"
                         default_val = f"{orig_val} {IconManager.arrow_right()} False"
                     else:
                     else:

+ 9 - 7
cli/core/module.py

@@ -249,11 +249,13 @@ class Module(ABC):
 
 
             # Re-sort sections after applying config (toggle values may have changed)
             # Re-sort sections after applying config (toggle values may have changed)
             template.variables.sort_sections()
             template.variables.sort_sections()
-            
+
             # Reset disabled bool variables to False to prevent confusion
             # Reset disabled bool variables to False to prevent confusion
             reset_vars = template.variables.reset_disabled_bool_variables()
             reset_vars = template.variables.reset_disabled_bool_variables()
             if reset_vars:
             if reset_vars:
-                logger.debug(f"Reset {len(reset_vars)} disabled bool variables to False")
+                logger.debug(
+                    f"Reset {len(reset_vars)} disabled bool variables to False"
+                )
 
 
         self._display_template_details(template, id)
         self._display_template_details(template, id)
 
 
@@ -651,11 +653,13 @@ class Module(ABC):
         # Re-sort sections after all overrides (toggle values may have changed)
         # Re-sort sections after all overrides (toggle values may have changed)
         if template.variables:
         if template.variables:
             template.variables.sort_sections()
             template.variables.sort_sections()
-            
+
             # Reset disabled bool variables to False to prevent confusion
             # Reset disabled bool variables to False to prevent confusion
             reset_vars = template.variables.reset_disabled_bool_variables()
             reset_vars = template.variables.reset_disabled_bool_variables()
             if reset_vars:
             if reset_vars:
-                logger.debug(f"Reset {len(reset_vars)} disabled bool variables to False")
+                logger.debug(
+                    f"Reset {len(reset_vars)} disabled bool variables to False"
+                )
 
 
         if not quiet:
         if not quiet:
             self._display_template_details(template, id)
             self._display_template_details(template, id)
@@ -1284,9 +1288,7 @@ class Module(ABC):
                 f"Template '{id}' could not be loaded: {exc}"
                 f"Template '{id}' could not be loaded: {exc}"
             ) from exc
             ) from exc
 
 
-    def _display_template_details(
-        self, template: Template, id: str
-    ) -> None:
+    def _display_template_details(self, template: Template, id: str) -> None:
         """Display template information panel and variables table.
         """Display template information panel and variables table.
 
 
         Args:
         Args:

+ 3 - 1
cli/core/prompt.py

@@ -87,7 +87,9 @@ class PromptHandler:
                     # Prompt for toggle variable using standard variable prompting logic
                     # Prompt for toggle variable using standard variable prompting logic
                     # This ensures consistent handling of description, extra text, validation hints, etc.
                     # This ensures consistent handling of description, extra text, validation hints, etc.
                     current_value = toggle_var.convert(toggle_var.value)
                     current_value = toggle_var.convert(toggle_var.value)
-                    new_value = self._prompt_variable(toggle_var, required=section.required)
+                    new_value = self._prompt_variable(
+                        toggle_var, required=section.required
+                    )
 
 
                     if new_value != current_value:
                     if new_value != current_value:
                         collected[toggle_var.name] = new_value
                         collected[toggle_var.name] = new_value

+ 5 - 3
cli/core/template.py

@@ -438,10 +438,12 @@ class Template:
             import importlib
             import importlib
 
 
             module = importlib.import_module(f"cli.modules.{kind}")
             module = importlib.import_module(f"cli.modules.{kind}")
-            
+
             # Check if module has schema-specific specs (multi-schema support)
             # Check if module has schema-specific specs (multi-schema support)
             # Try SCHEMAS constant first (uppercase), then schemas attribute
             # Try SCHEMAS constant first (uppercase), then schemas attribute
-            schemas = getattr(module, "SCHEMAS", None) or getattr(module, "schemas", None)
+            schemas = getattr(module, "SCHEMAS", None) or getattr(
+                module, "schemas", None
+            )
             if schemas and schema_version in schemas:
             if schemas and schema_version in schemas:
                 spec = schemas[schema_version]
                 spec = schemas[schema_version]
                 logger.debug(
                 logger.debug(
@@ -453,7 +455,7 @@ class Template:
                 logger.debug(
                 logger.debug(
                     f"Loaded and cached module spec for kind '{kind}' (default/no schema mapping)"
                     f"Loaded and cached module spec for kind '{kind}' (default/no schema mapping)"
                 )
                 )
-            
+
             return spec
             return spec
         except Exception as e:
         except Exception as e:
             raise ValueError(
             raise ValueError(

+ 5 - 2
cli/core/variable.py

@@ -1,10 +1,13 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
-from typing import Any, Dict, List, Optional, Set
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set
 from urllib.parse import urlparse
 from urllib.parse import urlparse
 import logging
 import logging
 import re
 import re
 
 
+if TYPE_CHECKING:
+    from cli.core.section import VariableSection
+
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 TRUE_VALUES = {"true", "1", "yes", "on"}
 TRUE_VALUES = {"true", "1", "yes", "on"}
@@ -408,7 +411,7 @@ class Variable:
 
 
     def get_parent(self) -> Optional["VariableSection"]:
     def get_parent(self) -> Optional["VariableSection"]:
         """Get the parent VariableSection that contains this variable.
         """Get the parent VariableSection that contains this variable.
-        
+
         Returns:
         Returns:
             The parent VariableSection if set, None otherwise
             The parent VariableSection if set, None otherwise
         """
         """

+ 1 - 2
cli/modules/compose/spec_v1_1.py

@@ -105,8 +105,7 @@ spec = OrderedDict(
             "title": "Ports",
             "title": "Ports",
             "toggle": "ports_enabled",
             "toggle": "ports_enabled",
             "needs": "network_mode=bridge",
             "needs": "network_mode=bridge",
-            "vars": {
-            },
+            "vars": {},
         },
         },
         "traefik": {
         "traefik": {
             "title": "Traefik",
             "title": "Traefik",

+ 0 - 9
network-v1

@@ -1,9 +0,0 @@
----
-services:
-  test_service:
-    networks:
-      bridge:
-
-networks:
-  bridge:
-    driver: bridge