Architecture Overview¶
This document describes the overall architecture of the PyPTS (Python Test Sequence) framework and recent improvements made to consolidate and clean up the codebase.
Core Components¶
The PyPTS framework is organized into several key modules:
Recipe Module (recipe.py)¶
The recipe.py module contains the core framework classes:
Recipe: Main class for loading and executing test recipes from YAML files
Runtime: Execution environment that manages global/local variables and event handling
Sequence: Container for ordered test steps with setup/teardown capabilities
Step: Abstract base class for all test steps
StepResult: Container for step execution results and metadata
ResultType: Enumeration of possible step outcomes (SKIP, DONE, PASS, FAIL, ERROR)
Steps Module (steps.py)¶
The steps.py module contains all concrete step implementations:
PythonModuleStep: Executes Python modules/methods dynamically
SequenceStep: Executes other sequences as steps
IndexedStep: Wrapper for running steps multiple times with indexed inputs
UserInteractionStep: Pauses execution for user input
WaitStep: Simple time delay step
Recent Architecture Improvements¶
Step Implementation Consolidation¶
Problem: Prior to the consolidation, step implementations were duplicated between recipe.py and steps.py, leading to:
Code duplication and maintenance burden
Inconsistent implementations with different capabilities
Inferior
__load_modulemethod in therecipe.pyversionRisk of using the wrong implementation
Solution: The architecture was refactored to eliminate duplication:
Removed duplicate step classes from
recipe.pyKept superior implementations in
steps.pyAdded import statement at the end of
recipe.pyto usesteps.pyimplementationsFixed circular import issues by placing imports after base class definitions
Module Loading Improvements¶
The PythonModuleStep now uses a robust __load_module implementation that includes:
- File Validation:
Checks if the module file exists before attempting import
Provides clear error messages for missing files
- Path Management:
Safely modifies
sys.pathduring importRestores original
sys.pathafter import completesHandles path conflicts and cleanup properly
- Module Conflict Detection:
Detects when a module with the same name is already loaded from a different path
Prevents import conflicts and provides clear error messages
Supports module reloading scenarios
- Enhanced Error Handling:
Comprehensive exception handling with detailed logging
Exception chaining for better debugging (
raise ... from e)Specific error types for different failure modes
- Logging Integration:
Debug logging for successful imports
Error logging with full context
Warning messages for potential issues
Import Management and Circular Dependencies¶
Challenge: The base classes in recipe.py needed to be available to steps.py, but recipe.py also needed to use the step implementations from steps.py.
- Solution:
Base classes (
Step,Runtime, etc.) remain inrecipe.pysteps.pyimports base classes:from pypts.recipe import Step, Runtime, ...recipe.pyimports implementations at the end:from pypts.steps import PythonModuleStep, ...Import placement after all base class definitions prevents circular dependency issues
Parameter Signature Compatibility¶
Standardization: Updated the base Step._step() method signature to use consistent parameter naming:
def _step(self, runtime, input, parent_step_result_uuid):
# Previously used 'parent_step', now uses 'parent_step_result_uuid'
# for consistency with steps.py implementations
Resource-Based Module Loading¶
Problem: The previous file-based module loading approach had several limitations:
Working Directory Dependency: Module paths were resolved relative to the current working directory
Distribution Complexity: Test modules needed to be available as separate files during deployment
Path Management Issues: Complex
sys.pathmanipulation with potential conflictsDeployment Fragility: Risk of missing test files in different environments
Solution: Implemented a resource-based module loading system using importlib.resources:
Recipe Configuration: Added
test_packagefield to recipe YAML filesPackage-Based Loading: Test modules are now loaded as proper Python package resources
Simplified Path Resolution: Module paths are resolved within the specified package
Robust Error Handling: Clear error messages for missing packages or modules
Implementation Details:
The PythonModuleStep now uses a completely rewritten __load_module method that:
- Package Validation:
Verifies the specified
test_packageexists and is accessibleUses
importlib.resources.files()to validate package availabilityProvides clear error messages for missing or inaccessible packages
- Resource-Based Import:
Converts file paths (e.g.,
tests/test_status.py) to module names (test_status)Constructs full module names using the package prefix (
fsi_pts.tests.test_status)Uses standard Python import mechanisms instead of file system operations
- Simplified Architecture:
Eliminated complex
sys.pathmanipulationRemoved module conflict detection (handled by Python’s import system)
Streamlined error handling with proper exception chaining
Recipe configuration fields:
# RecipeHeader fragment
test_package: fsi_pts.tests
# PythonModuleStep fragment
module: test_status.py # Resolves to fsi_pts.tests.test_status
Benefits:
Package Consistency: Tests are bundled as proper package resources
Distribution Simplicity: No separate file management required
Environment Independence: No dependency on current working directory
Deployment Robustness: Tests are guaranteed to be available if the package is installed
Python Standards Compliance: Uses standard Python import mechanisms
Current Architecture Benefits¶
Clean Separation of Concerns¶
Base Framework (
recipe.py): Core recipe execution logicStep Implementations (
steps.py): Specific step behaviors and capabilitiesSingle Source of Truth: Each step type has exactly one implementation
Maintainability Improvements¶
No Code Duplication: Changes only need to be made in one place
Consistent Behavior: All instances of a step type use the same implementation
Better Testing: Single implementation to test and validate
Robustness Enhancements¶
Superior Module Loading: File validation, error handling, and cleanup
Conflict Detection: Prevents module import conflicts
Comprehensive Logging: Better debugging and troubleshooting capabilities
GUI Architecture¶
The GUI layer now has a clearer separation than the older monolithic window approach.
Runtime GUI¶
The runtime application is built around pypts.gui.MainWindow and a set of
reusable widgets in pypts.gui_components. MainWindow acts primarily as
the orchestrator:
builds the menu, toolbar, central splitter, and status bar
switches between idle/running/prompt/results screens
delegates rendering to dedicated widgets such as the step table, results panel, interaction panel, and log panel
receives ViewModel dictionaries from the event-proxy layer and forwards them to the appropriate panel/widget
This makes the runtime GUI panelized: the window is composed from smaller functional panels instead of one large central widget with all rendering logic inline.
YamVIEW relationship¶
YamVIEW remains a separate recipe-editor application under
pypts.YamVIEW. It is still launched as a separate process by the runtime
GUI, but it now consumes the same theme layer so both applications use the same
visual palette and dark-mode behavior.
At present:
shared: palette, theme logic, dark-mode behavior
separate: most concrete widgets and layouts
See GUI Architecture for the full GUI-oriented view of this split.
Future Considerations¶
Path Resolution¶
✅ IMPLEMENTED: Package-aware loading is now available through the test_package configuration field.
Future improvements may include:
Recipe-relative paths: Resolve module paths relative to the recipe file location (for legacy file-based loading)
Multiple package support: Allow recipes to specify multiple test packages
Configurable search paths: Allow recipes to specify additional module search directories for mixed loading strategies
Extension Points¶
The consolidated architecture provides clear extension points:
New Step Types: Add to
steps.pyfollowing existing patternsCustom Loaders: Override default file loading behavior in
RecipeclassEvent Handlers: Extend the event system for custom runtime behavior
Migration Guide¶
For existing code using the old architecture:
No changes required for recipe YAML files
Import updates: Change imports from
recipetostepsif directly importing step classesParameter names: Update any custom step implementations to use
parent_step_result_uuid
# Old
from pypts.recipe import PythonModuleStep
# New
from pypts.steps import PythonModuleStep
For migrating to resource-based module loading:
Package Structure: Organize test modules as Python packages with proper
__init__.pyfilesRecipe Configuration: Add
test_packagefield to recipe YAML filesModule Paths: Update module paths to be relative to the test package (remove directory prefixes)
# File-based module field
module: tests/my_test.py
# Resource-based header and module fields
test_package: my_package.tests
module: my_test.py # Resolves to my_package.tests.my_test