Set all dependencies for a feature at once, replacing any existing. Validates: self-reference, existence of all dependencies, circular dependencies, max limit.
(project_name: str, feature_id: int, update: DependencyUpdate)
| 678 | |
| 679 | @router.put("/{feature_id}/dependencies") |
| 680 | async def set_dependencies(project_name: str, feature_id: int, update: DependencyUpdate): |
| 681 | """Set all dependencies for a feature at once, replacing any existing. |
| 682 | |
| 683 | Validates: self-reference, existence of all dependencies, circular dependencies, max limit. |
| 684 | """ |
| 685 | project_name = validate_project_name(project_name) |
| 686 | project_dir = _get_project_path(project_name) |
| 687 | |
| 688 | if not project_dir: |
| 689 | raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found in registry") |
| 690 | |
| 691 | if not project_dir.exists(): |
| 692 | raise HTTPException(status_code=404, detail="Project directory not found") |
| 693 | |
| 694 | dependency_ids = update.dependency_ids |
| 695 | |
| 696 | # Security: Self-reference check |
| 697 | if feature_id in dependency_ids: |
| 698 | raise HTTPException(status_code=400, detail="A feature cannot depend on itself") |
| 699 | |
| 700 | # Check for duplicates |
| 701 | if len(dependency_ids) != len(set(dependency_ids)): |
| 702 | raise HTTPException(status_code=400, detail="Duplicate dependencies not allowed") |
| 703 | |
| 704 | would_create_circular_dependency, _ = _get_dependency_resolver() |
| 705 | _, Feature = _get_db_classes() |
| 706 | |
| 707 | try: |
| 708 | with get_db_session(project_dir) as session: |
| 709 | feature = session.query(Feature).filter(Feature.id == feature_id).first() |
| 710 | if not feature: |
| 711 | raise HTTPException(status_code=404, detail=f"Feature {feature_id} not found") |
| 712 | |
| 713 | # Validate all dependencies exist |
| 714 | all_feature_ids = {f.id for f in session.query(Feature).all()} |
| 715 | missing = [d for d in dependency_ids if d not in all_feature_ids] |
| 716 | if missing: |
| 717 | raise HTTPException(status_code=400, detail=f"Dependencies not found: {missing}") |
| 718 | |
| 719 | # Check for circular dependencies |
| 720 | all_features = [f.to_dict() for f in session.query(Feature).all()] |
| 721 | # Temporarily update the feature's dependencies for cycle check |
| 722 | test_features = [] |
| 723 | for f in all_features: |
| 724 | if f["id"] == feature_id: |
| 725 | test_features.append({**f, "dependencies": dependency_ids}) |
| 726 | else: |
| 727 | test_features.append(f) |
| 728 | |
| 729 | for dep_id in dependency_ids: |
| 730 | # source_id = feature_id (gaining dep), target_id = dep_id (being depended upon) |
| 731 | if would_create_circular_dependency(test_features, feature_id, dep_id): |
| 732 | raise HTTPException( |
| 733 | status_code=400, |
| 734 | detail=f"Cannot add dependency {dep_id}: would create circular dependency" |
| 735 | ) |
| 736 | |
| 737 | # Set dependencies |
nothing calls this directly
no test coverage detected