refactor: split monolithic app.py into modular Flask application with factory pattern
- Created app/ package with 6 modules: - __init__.py: Application factory (create_app function) - routes.py: All route handlers (473 lines) - models.py: Data persistence layer (41 lines) - validators.py: Input validation functions (48 lines) - email_service.py: Email sending functions (85 lines) - utils.py: Utility functions (30 lines) - Implemented Flask application factory pattern - Created run.py as minimal entry point - Updated all test imports to use new module structure - Fixed template/static folder paths for package structure - All 102 tests passing - Improved maintainability with clear separation of concerns - Follows Flask best practices for scalable applications
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
<objective>
|
||||
Refactor the monolithic app.py (664 lines) into a well-organized, modular Flask application structure using the application factory pattern. The goal is to improve code maintainability, testability, and separation of concerns while preserving all existing functionality.
|
||||
|
||||
This refactoring will make the codebase easier to navigate, enable better unit testing of individual components, and follow Flask best practices for scalable applications.
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
Current state:
|
||||
- Single app.py file contains routes, validation logic, email functions, storage helpers, and Flask app initialization
|
||||
- 102 passing tests that import directly from app module
|
||||
- Flask application with CSRF protection, rate limiting, file uploads, and email notifications
|
||||
- Tech stack: Flask 3.0.0, Flask-Mail, Flask-WTF, PyYAML
|
||||
|
||||
Target state:
|
||||
- Organized app/ package structure with separated concerns
|
||||
- Flask application factory pattern (create_app function)
|
||||
- Maintained backward compatibility for all existing functionality
|
||||
- All 102 tests updated and passing
|
||||
|
||||
Review the current implementation:
|
||||
@app.py
|
||||
</context>
|
||||
|
||||
<requirements>
|
||||
1. **Create app/ package structure**:
|
||||
- `app/__init__.py` - Application factory (create_app function), Flask app initialization
|
||||
- `app/routes.py` - All route handlers (@app.route decorators)
|
||||
- `app/models.py` - Data models and storage functions (save/load application data)
|
||||
- `app/validators.py` - All validation functions (email, phone, year, zip, file extensions)
|
||||
- `app/email_service.py` - Email-related functions (send_resume_email, send_hr_notification)
|
||||
- `app/utils.py` - Helper functions (path helpers, rate limiting, file handling)
|
||||
|
||||
2. **Implement Flask application factory pattern**:
|
||||
- Create `create_app(config_class=Config)` function in `app/__init__.py`
|
||||
- Initialize extensions (mail, csrf) within factory
|
||||
- Register blueprints or routes within factory
|
||||
- Update main entry point to use factory
|
||||
|
||||
3. **Maintain all existing functionality**:
|
||||
- All routes must work identically
|
||||
- CSRF protection, rate limiting, file uploads, email notifications unchanged
|
||||
- Session handling, flash messages, redirects work as before
|
||||
- Error handlers preserved
|
||||
|
||||
4. **Update test suite**:
|
||||
- Modify test imports to use new module structure
|
||||
- Update fixtures to work with application factory
|
||||
- Ensure all 102 tests pass after refactoring
|
||||
- No changes to test logic, only imports and app initialization
|
||||
|
||||
5. **Update main entry point**:
|
||||
- Create `run.py` or update existing entry point to use `create_app()`
|
||||
- Ensure development server still runs with `python run.py` or `flask run`
|
||||
|
||||
6. **Preserve imports and dependencies**:
|
||||
- Keep all existing imports (Flask, yaml, werkzeug, etc.)
|
||||
- Maintain proper circular import prevention
|
||||
- Use relative imports within app/ package
|
||||
</requirements>
|
||||
|
||||
<implementation>
|
||||
**Module separation strategy**:
|
||||
|
||||
1. **app/__init__.py** should contain:
|
||||
- Import statements for Flask, extensions
|
||||
- Extension instances (mail, csrf) initialized but not bound
|
||||
- `create_app(config_class=Config)` function that:
|
||||
- Creates Flask app instance
|
||||
- Loads configuration
|
||||
- Initializes extensions (mail.init_app, csrf.init_app)
|
||||
- Registers routes (import and register blueprint or routes)
|
||||
- Registers error handlers
|
||||
- Returns app instance
|
||||
|
||||
2. **app/routes.py** should contain:
|
||||
- All @app.route decorated functions
|
||||
- Import necessary validators, models, email_service, utils
|
||||
- Either use Blueprint or accept app parameter for route registration
|
||||
- Preserve all route logic exactly as is
|
||||
|
||||
3. **app/models.py** should contain:
|
||||
- Path helper functions (get_application_path, get_data_file_path, get_attachments_path)
|
||||
- Data persistence functions (load_application_data, save_application_data)
|
||||
- Any data structure or schema-related code
|
||||
|
||||
4. **app/validators.py** should contain:
|
||||
- validate_email, validate_phone, validate_year, validate_zip
|
||||
- allowed_file function
|
||||
- Each function should be independent and easily testable
|
||||
|
||||
5. **app/email_service.py** should contain:
|
||||
- send_resume_email function
|
||||
- send_hr_notification function
|
||||
- Requires access to mail instance and app.config
|
||||
- Use current_app for accessing config when needed
|
||||
|
||||
6. **app/utils.py** should contain:
|
||||
- check_rate_limit function
|
||||
- Any other utility functions not fitting elsewhere
|
||||
|
||||
**Important considerations**:
|
||||
|
||||
- **Circular imports**: To avoid circular imports between routes and models:
|
||||
- Routes import from models, validators, email_service, utils
|
||||
- Models should NOT import from routes
|
||||
- Use `from flask import current_app` to access app instance in helper functions
|
||||
|
||||
- **Extension access**: For mail, csrf in separate modules:
|
||||
- Initialize extensions in `app/__init__.py`
|
||||
- Import and use them in routes/email_service: `from app import mail, csrf`
|
||||
- OR pass as parameters where needed
|
||||
|
||||
- **Configuration access**: Use `current_app.config` in utility functions instead of `app.config`
|
||||
|
||||
- **Route registration**: Two approaches (choose one):
|
||||
- Blueprint approach: Create Blueprint in routes.py, register in factory
|
||||
- Direct approach: Import route functions and call them with app instance
|
||||
|
||||
- **Error handlers**: Register in `create_app()` factory function
|
||||
|
||||
**WHY this structure**:
|
||||
- Separation of concerns makes each module independently testable
|
||||
- Application factory enables multiple app instances for testing
|
||||
- Smaller files are easier to navigate and maintain
|
||||
- Follows Flask community best practices for production applications
|
||||
- Enables future scaling (add more routes, validators, services without bloating single file)
|
||||
</implementation>
|
||||
|
||||
<migration_steps>
|
||||
Execute refactoring in this order to minimize breaking changes:
|
||||
|
||||
1. **Create app/ package**:
|
||||
- Create `app/` directory
|
||||
- Create `app/__init__.py` with imports and extension initialization
|
||||
|
||||
2. **Extract validators** (least dependencies):
|
||||
- Move validation functions to `app/validators.py`
|
||||
- Update imports in app.py
|
||||
- Run tests to verify
|
||||
|
||||
3. **Extract models** (storage layer):
|
||||
- Move path helpers and data functions to `app/models.py`
|
||||
- Update imports in app.py
|
||||
- Run tests to verify
|
||||
|
||||
4. **Extract email service**:
|
||||
- Move email functions to `app/email_service.py`
|
||||
- Ensure mail instance is accessible
|
||||
- Update imports in app.py
|
||||
- Run tests to verify
|
||||
|
||||
5. **Extract utilities**:
|
||||
- Move rate limiting and other utils to `app/utils.py`
|
||||
- Update imports in app.py
|
||||
- Run tests to verify
|
||||
|
||||
6. **Extract routes**:
|
||||
- Move all route handlers to `app/routes.py`
|
||||
- Keep route registration in app.py temporarily
|
||||
- Run tests to verify
|
||||
|
||||
7. **Implement application factory**:
|
||||
- Create `create_app()` in `app/__init__.py`
|
||||
- Move app initialization into factory
|
||||
- Update `app.py` or create `run.py` to use factory
|
||||
- Run tests to verify
|
||||
|
||||
8. **Update all tests**:
|
||||
- Update imports in all test files
|
||||
- Update app fixture in conftest.py to use factory
|
||||
- Run full test suite
|
||||
- Fix any remaining import issues
|
||||
|
||||
9. **Final cleanup**:
|
||||
- Remove old app.py or convert to minimal run.py
|
||||
- Verify all functionality works
|
||||
- Run full test suite one final time
|
||||
</migration_steps>
|
||||
|
||||
<test_updates>
|
||||
Update test files to work with new structure:
|
||||
|
||||
**tests/conftest.py** changes:
|
||||
```python
|
||||
from app import create_app
|
||||
from app import mail
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
"""Create and configure a test Flask application instance."""
|
||||
test_app = create_app()
|
||||
# ... rest of configuration
|
||||
yield test_app
|
||||
```
|
||||
|
||||
**All test files** should update imports:
|
||||
- Change `from app import validate_email` → `from app.validators import validate_email`
|
||||
- Change `from app import send_resume_email` → `from app.email_service import send_resume_email`
|
||||
- Change `from app import load_application_data` → `from app.models import load_application_data`
|
||||
- And so on for all imports
|
||||
|
||||
Run tests after each major change to catch import errors early.
|
||||
</test_updates>
|
||||
|
||||
<output>
|
||||
Create the following new files:
|
||||
|
||||
- `./app/__init__.py` - Application factory and extension initialization
|
||||
- `./app/routes.py` - All route handlers
|
||||
- `./app/models.py` - Data persistence layer
|
||||
- `./app/validators.py` - Validation functions
|
||||
- `./app/email_service.py` - Email sending functions
|
||||
- `./app/utils.py` - Utility functions (rate limiting, etc.)
|
||||
|
||||
Modify:
|
||||
- `./app.py` → Convert to `./run.py` (or keep as minimal entry point using factory)
|
||||
- `./tests/conftest.py` - Update app fixture to use create_app()
|
||||
- `./tests/*.py` - Update all imports to new module structure
|
||||
|
||||
Delete after successful migration:
|
||||
- Original `./app.py` (only if converted to run.py)
|
||||
</output>
|
||||
|
||||
<verification>
|
||||
Before declaring complete, verify:
|
||||
|
||||
1. **All tests pass**: Run `pytest tests/ -v` and confirm all 102 tests pass
|
||||
2. **Application runs**: Start server with `python run.py` or `flask run` and verify pages load
|
||||
3. **No circular imports**: Python can import app package without errors
|
||||
4. **All routes work**: Test critical user flow:
|
||||
- Submit email on page 1
|
||||
- Fill personal info on page 2
|
||||
- Fill motivation on page 3
|
||||
- Upload file and submit on page 4
|
||||
- View confirmation on page 5
|
||||
- Access /application/<session_id>/ view
|
||||
- Download uploaded file
|
||||
5. **Email functionality**: HR notification sent on submission (check logs)
|
||||
6. **No broken imports**: All test files import successfully
|
||||
|
||||
If any tests fail, carefully review:
|
||||
- Import statements in test files
|
||||
- Application context in helper functions (use current_app)
|
||||
- Extension initialization in factory
|
||||
- Route registration in factory
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- app/ package created with 6 modules (__init__, routes, models, validators, email_service, utils)
|
||||
- Application factory pattern implemented (create_app function)
|
||||
- Original app.py converted to minimal run.py entry point
|
||||
- All 102 tests passing with updated imports
|
||||
- Application runs and all features work identically
|
||||
- Code is more maintainable with clear separation of concerns
|
||||
- No circular import errors
|
||||
- Flask development server starts successfully
|
||||
</success_criteria>
|
||||
Completed: Sa 27 Dez 2025 22:29:29 CET
|
||||
Reference in New Issue
Block a user