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:
2025-12-27 22:36:05 +01:00
parent 733ee756c7
commit f1d8bae6a1
18 changed files with 1254 additions and 229 deletions
@@ -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