diff --git a/examples/project.py b/examples/project.py index 3866365f..cfc843ff 100644 --- a/examples/project.py +++ b/examples/project.py @@ -1,853 +1,208 @@ """ -Comprehensive Integration Test for python-tfe Projects CRUD Operations +Real-time Project SDK Integration Example -This file tests all CRUD operations from src/tfe/resources/projects.py: -- List: Get all projects in an organization -- Create: Add new projects with validation -- Read: Get specific project details -- Update: Modify existing projects -- Delete: Remove projects +This example demonstrates how to use the TFE Python SDK for project operations +with real API calls. This is NOT a unit test - it uses the actual SDK client +to perform CRUD operations on real projects. Setup Instructions: -1. Create a test organization in HCP Terraform (https://app.terraform.io) -2. Generate an organization or user API token with appropriate permissions -3. Set environment variables: +1. Set environment variables: export TFE_TOKEN="your-api-token-here" export TFE_ORG="your-test-organization-name" -4. Run the tests: - pytest examples/project.py -v -s +2. Run the example: + python examples/project.py Important Notes: -- These tests make real API calls and create/delete actual resources +- This makes real API calls and creates/deletes actual resources - Always use a dedicated test organization, never production -- Tests will fail if you don't have proper permissions -- Clean up is automatic, but verify resources are deleted after testing +- Resources are automatically cleaned up after demonstration """ import os import uuid -import pytest - -from tfe._http import HTTPTransport -from tfe.config import TFEConfig -from tfe.resources.projects import Projects -from tfe.types import ( - ProjectAddTagBindingsOptions, +from tfe import TFEClient +from tfe.models.project import ( ProjectCreateOptions, ProjectListOptions, ProjectUpdateOptions, - TagBinding, ) -@pytest.fixture -def integration_client(): - """Create a real Projects client for integration testing""" - token = os.environ.get("TFE_TOKEN") - org = os.environ.get("TFE_ORG") - - if not token: - pytest.skip( - "TFE_TOKEN environment variable is required. " - "Get your token from HCP Terraform: Settings โ†’ API Tokens" - ) - - if not org: - pytest.skip( - "TFE_ORG environment variable is required. " - "Use your organization name from HCP Terraform URL" - ) - - print(f"\n๐Ÿ”ง Testing against organization: {org}") - print(f"๐Ÿ”ง Using token: {token[:10]}...") - - config = TFEConfig() - - try: - transport = HTTPTransport( - config.address, - token, - timeout=config.timeout, - verify_tls=config.verify_tls, - user_agent_suffix=None, - max_retries=3, - backoff_base=0.1, - backoff_cap=1.0, - backoff_jitter=True, - http2=False, - proxies=None, - ca_bundle=None, - ) - except Exception as e: - pytest.fail(f"Failed to create HTTP transport: {e}") - - return Projects(transport), org - - -def test_list_projects_integration(integration_client): - """Test LIST operation - Get all projects in organization - - This is the safest test to run first - it only reads data. - Tests: projects.list(organization, options) - """ - projects, org = integration_client - - try: - # Test basic list without options - print("๐Ÿ“‹ Testing LIST operation: basic list") - project_list = list(projects.list(org)) - print(f"โœ… Found {len(project_list)} projects in organization '{org}'") - - assert isinstance(project_list, list) - - if project_list: - project = project_list[0] - assert hasattr(project, "id"), "Project should have an ID" - assert hasattr(project, "name"), "Project should have a name" - assert hasattr(project, "organization"), ( - "Project should have an organization" - ) - assert hasattr(project, "description"), "Project should have a description" - assert hasattr(project, "created_at"), "Project should have created_at" - assert hasattr(project, "updated_at"), "Project should have updated_at" - print(f"๐Ÿ“‹ Example project: {project.name} (ID: {project.id})") - print(f"๐Ÿ“‹ Created: {project.created_at}, Updated: {project.updated_at}") - else: - print("๐Ÿ“‹ No projects found - this is normal for a new organization") - - # Test list with options - print("๐Ÿ“‹ Testing LIST operation: with options") - list_options = ProjectListOptions(page_size=5) - project_list_with_options = list(projects.list(org, list_options)) - print( - f"โœ… List with options returned {len(project_list_with_options)} projects" - ) - - except Exception as e: - pytest.fail( - f"LIST operation failed. Check your TFE_TOKEN and TFE_ORG. Error: {e}" - ) - - -def test_create_project_integration(integration_client): - """Test CREATE operation - Add new projects - - Tests: projects.create(organization, options) - Validates: ProjectCreateOptions with name and description - """ - projects, org = integration_client - - unique_id = str(uuid.uuid4())[:8] - test_name = f"create-test-{unique_id}" - test_description = f"Integration test project created at {unique_id}" - project_id = None - - try: - # Test CREATE operation - print(f"๐Ÿ”จ Testing CREATE operation: {test_name}") - create_options = ProjectCreateOptions( - name=test_name, description=test_description - ) - created_project = projects.create(org, create_options) - - # Validate created project - assert created_project.name == test_name, ( - f"Expected name {test_name}, got {created_project.name}" - ) - assert created_project.description == test_description, ( - f"Expected description {test_description}, got {created_project.description}" - ) - assert created_project.organization == org, ( - f"Expected org {org}, got {created_project.organization}" - ) - assert created_project.id.startswith("prj-"), ( - f"Project ID should start with 'prj-', got {created_project.id}" - ) - assert created_project.workspace_count == 0, ( - "New project should have 0 workspaces" - ) - - project_id = created_project.id - print(f"โœ… CREATE successful: {project_id}") - print( - f"โœ… Project details: {created_project.name} - {created_project.description}" - ) - - except Exception as e: - pytest.fail(f"CREATE operation failed: {e}") - - finally: - # Clean up created project - if project_id: - try: - print(f"๐Ÿ—‘๏ธ Cleaning up created project: {project_id}") - projects.delete(project_id) - print("โœ… Cleanup successful") - except Exception as e: - print(f"โŒ Warning: Failed to clean up project {project_id}: {e}") - - -def test_read_project_integration(integration_client): - """Test READ operation - Get specific project details - - Tests: projects.read(project_id, include) - Creates a project, reads it, then cleans up - """ - projects, org = integration_client - - unique_id = str(uuid.uuid4())[:8] - test_name = f"read-test-{unique_id}" - project_id = None - - try: - # Create a project to read - print(f"๏ฟฝ Creating project for READ test: {test_name}") - create_options = ProjectCreateOptions( - name=test_name, description="Project for read test" - ) - created_project = projects.create(org, create_options) - project_id = created_project.id - - # Test READ operation - print(f"๐Ÿ“– Testing READ operation: {project_id}") - read_project = projects.read(project_id) - - # Validate read project - assert read_project.id == project_id, ( - f"Expected ID {project_id}, got {read_project.id}" - ) - assert read_project.name == test_name, ( - f"Expected name {test_name}, got {read_project.name}" - ) - assert read_project.organization == org, ( - f"Expected org {org}, got {read_project.organization}" - ) - assert hasattr(read_project, "created_at"), "Project should have created_at" - assert hasattr(read_project, "updated_at"), "Project should have updated_at" - - print(f"โœ… READ successful: {read_project.name}") - print(f"โœ… Project created: {read_project.created_at}") - - # Note: Projects API doesn't support include parameters in the current API version - print("โœ… READ operation completed successfully") - - except Exception as e: - pytest.fail(f"READ operation failed: {e}") - - finally: - # Clean up created project - if project_id: - try: - print(f"๐Ÿ—‘๏ธ Cleaning up read test project: {project_id}") - projects.delete(project_id) - print("โœ… Cleanup successful") - except Exception as e: - print(f"โŒ Warning: Failed to clean up project {project_id}: {e}") - - -def test_update_project_integration(integration_client): - """Test UPDATE operation - Modify existing projects - - Tests: projects.update(project_id, options) - Validates: ProjectUpdateOptions with name and description changes - """ - projects, org = integration_client - - unique_id = str(uuid.uuid4())[:8] - original_name = f"update-test-{unique_id}" - updated_name = f"updated-test-{unique_id}" - original_description = "Original description for update test" - updated_description = "Updated description for update test" - project_id = None - - try: - # Create a project to update - print(f"๐Ÿ”จ Creating project for UPDATE test: {original_name}") - create_options = ProjectCreateOptions( - name=original_name, description=original_description - ) - created_project = projects.create(org, create_options) - project_id = created_project.id - - # Test UPDATE operation - name only - print("โœ๏ธ Testing UPDATE operation: name only") - update_options = ProjectUpdateOptions(name=updated_name) - updated_project = projects.update(project_id, update_options) - - assert updated_project.id == project_id, ( - f"Project ID should remain {project_id}" - ) - assert updated_project.name == updated_name, ( - f"Expected updated name {updated_name}, got {updated_project.name}" - ) - assert updated_project.description == original_description, ( - "Description should remain unchanged" - ) - print(f"โœ… UPDATE name successful: {updated_project.name}") - - # Test UPDATE operation - description only - print("โœ๏ธ Testing UPDATE operation: description only") - update_options = ProjectUpdateOptions(description=updated_description) - updated_project = projects.update(project_id, update_options) - - assert updated_project.name == updated_name, "Name should remain unchanged" - assert updated_project.description == updated_description, ( - f"Expected updated description {updated_description}, got {updated_project.description}" - ) - print("โœ… UPDATE description successful") - - # Test UPDATE operation - both name and description - final_name = f"final-{unique_id}" - final_description = "Final description for update test" - print("โœ๏ธ Testing UPDATE operation: both name and description") - update_options = ProjectUpdateOptions( - name=final_name, description=final_description - ) - updated_project = projects.update(project_id, update_options) - - assert updated_project.name == final_name, ( - f"Expected final name {final_name}, got {updated_project.name}" - ) - assert updated_project.description == final_description, ( - f"Expected final description {final_description}, got {updated_project.description}" - ) - print(f"โœ… UPDATE both fields successful: {updated_project.name}") - - except Exception as e: - pytest.fail(f"UPDATE operation failed: {e}") - - finally: - # Clean up created project - if project_id: - try: - print(f"๐Ÿ—‘๏ธ Cleaning up update test project: {project_id}") - projects.delete(project_id) - print("โœ… Cleanup successful") - except Exception as e: - print(f"โŒ Warning: Failed to clean up project {project_id}: {e}") - - -def test_delete_project_integration(integration_client): - """Test DELETE operation - Remove projects - - Tests: projects.delete(project_id) - Creates a project, deletes it, verifies it's gone - """ - projects, org = integration_client - - unique_id = str(uuid.uuid4())[:8] - test_name = f"delete-test-{unique_id}" - project_id = None - - try: - # Create a project to delete - print(f"๐Ÿ”จ Creating project for DELETE test: {test_name}") - create_options = ProjectCreateOptions( - name=test_name, description="Project for delete test" - ) - created_project = projects.create(org, create_options) - project_id = created_project.id - print(f"โœ… Project created for deletion: {project_id}") - - # Verify project exists - print("๐Ÿ“– Verifying project exists before deletion") - read_project = projects.read(project_id) - assert read_project.id == project_id - print(f"โœ… Project confirmed to exist: {read_project.name}") +def project_sdk_example(): + """Demonstrate Project SDK operations with real API calls.""" - # Test DELETE operation - print(f"๐Ÿ—‘๏ธ Testing DELETE operation: {project_id}") - projects.delete(project_id) - print("โœ… DELETE operation completed") - - # Verify project is deleted - print("๐Ÿ“– Verifying project is deleted") - try: - projects.read(project_id) - pytest.fail("Project should not exist after deletion") - except Exception as e: - if "404" in str(e) or "not found" in str(e).lower(): - print("โœ… Project successfully deleted - confirmed by 404 error") - else: - raise e - - # Clear project_id since it's been deleted - project_id = None - - except Exception as e: - pytest.fail(f"DELETE operation failed: {e}") - - finally: - # Additional cleanup attempt (should be unnecessary) - if project_id: - try: - print(f"๐Ÿ—‘๏ธ Additional cleanup attempt: {project_id}") - projects.delete(project_id) - except Exception: - pass # Project might already be deleted + # Initialize SDK client + token = os.getenv("TFE_TOKEN") + org_name = os.getenv("TFE_ORG") + if not token or not org_name: + print("โŒ Please set TFE_TOKEN and TFE_ORG environment variables") + print(" export TFE_TOKEN='your-hcp-terraform-token'") + print(" export TFE_ORG='your-organization-name'") + return -def test_comprehensive_crud_integration(integration_client): - """Test all CRUD operations in sequence + print("๐Ÿš€ TFE Python SDK - Project Operations Example") + print("=" * 50) + print(f"๐Ÿ”ง Organization: {org_name}") + print(f"๐Ÿ”ง Token: {token[:10]}...") - โš ๏ธ WARNING: This test creates and deletes real resources! - Tests complete workflow: CREATE โ†’ READ โ†’ UPDATE โ†’ LIST โ†’ DELETE - """ - projects, org = integration_client + # Create SDK client + client = TFEClient() unique_id = str(uuid.uuid4())[:8] - test_name = f"comprehensive-{unique_id}" - updated_name = f"comprehensive-updated-{unique_id}" - test_description = f"Comprehensive CRUD test {unique_id}" - updated_description = f"Updated comprehensive CRUD test {unique_id}" + test_project_name = f"sdk-example-{unique_id}" + test_description = f"SDK example project created at {unique_id}" project_id = None try: - print(f"๐Ÿ”„ Starting comprehensive CRUD test: {test_name}") - - # 1. CREATE - print("1๏ธโƒฃ CREATE: Creating project") + print("\n1๏ธโƒฃ LIST: Getting existing projects...") + # List existing projects + existing_projects = list(client.projects.list(org_name)) + print(f"โœ… Found {len(existing_projects)} existing projects") + + if existing_projects: + print("๐Ÿ“‹ Example existing projects:") + for i, project in enumerate(existing_projects[:3]): # Show first 3 + print(f" - {project.name} (ID: {project.id})") + if i == 2 and len(existing_projects) > 3: + print(f" ... and {len(existing_projects) - 3} more") + + print(f"\n2๏ธโƒฃ CREATE: Creating new project '{test_project_name}'...") + # Create a new project create_options = ProjectCreateOptions( - name=test_name, description=test_description + name=test_project_name, description=test_description ) - created_project = projects.create(org, create_options) + created_project = client.projects.create(org_name, create_options) project_id = created_project.id - assert created_project.name == test_name - assert created_project.description == test_description - print(f"โœ… CREATE: {project_id}") - - # 2. READ - print("2๏ธโƒฃ READ: Reading created project") - read_project = projects.read(project_id) - - assert read_project.id == project_id - assert read_project.name == test_name - assert read_project.description == test_description - print(f"โœ… READ: {read_project.name}") - - # 3. UPDATE - print("3๏ธโƒฃ UPDATE: Updating project") + print("โœ… Project created successfully!") + print(f" ID: {created_project.id}") + print(f" Name: {created_project.name}") + print(f" Description: {created_project.description}") + print(f" Organization: {created_project.organization}") + print(f" Created: {created_project.created_at}") + + print("\n3๏ธโƒฃ READ: Reading project details...") + # Read the created project + read_project = client.projects.read(project_id) + print("โœ… Project read successfully:") + print(f" Name: {read_project.name}") + print(f" Workspace Count: {read_project.workspace_count}") + print(f" Updated: {read_project.updated_at}") + + print("\n4๏ธโƒฃ UPDATE: Updating project...") + # Update the project + updated_name = f"sdk-updated-{unique_id}" + updated_description = f"SDK example project updated at {unique_id}" update_options = ProjectUpdateOptions( name=updated_name, description=updated_description ) - updated_project = projects.update(project_id, update_options) + updated_project = client.projects.update(project_id, update_options) + + print("โœ… Project updated successfully!") + print(f" New Name: {updated_project.name}") + print(f" New Description: {updated_project.description}") - assert updated_project.id == project_id - assert updated_project.name == updated_name - assert updated_project.description == updated_description - print(f"โœ… UPDATE: {updated_project.name}") + print("\n5๏ธโƒฃ LIST WITH OPTIONS: Testing list with pagination...") + # Test list with options + list_options = ProjectListOptions(page_size=5) + projects_with_options = list(client.projects.list(org_name, list_options)) + print(f"โœ… List with options returned {len(projects_with_options)} projects") - # 4. LIST (verify updated project appears) - print("4๏ธโƒฃ LIST: Verifying project appears in list") - project_list = list(projects.list(org)) + # Verify our updated project appears in the list found_project = None - for p in project_list: - if p.id == project_id: - found_project = p + for project in projects_with_options: + if project.id == project_id: + found_project = project break - assert found_project is not None, ( - f"Updated project {project_id} should appear in list" - ) - assert found_project.name == updated_name - print("โœ… LIST: Found updated project in list") + if found_project: + print(f"โœ… Confirmed updated project appears in list: {found_project.name}") + else: + print("โš ๏ธ Updated project not found in list (may be on another page)") - # 5. DELETE - print("5๏ธโƒฃ DELETE: Deleting project") - projects.delete(project_id) - print("โœ… DELETE: Project deleted") + print("\n6๏ธโƒฃ DELETE: Cleaning up created project...") + # Delete the project + client.projects.delete(project_id) + print("โœ… Project deleted successfully!") - # 6. Verify deletion - print("6๏ธโƒฃ VERIFY: Confirming deletion") + # Verify deletion + print("๐Ÿ” Verifying project deletion...") try: - projects.read(project_id) - pytest.fail("Project should not exist after deletion") + client.projects.read(project_id) + print("โŒ Warning: Project still exists after deletion") except Exception as e: if "404" in str(e) or "not found" in str(e).lower(): - print("โœ… VERIFY: Deletion confirmed") + print("โœ… Confirmed: Project successfully deleted") else: - raise e + print(f"โš ๏ธ Unexpected error during verification: {e}") project_id = None # Clear since deleted - print("๐ŸŽ‰ Comprehensive CRUD test completed successfully!") - except Exception as e: - pytest.fail(f"Comprehensive CRUD test failed: {e}") + print("\n๐ŸŽ‰ Project SDK Example Completed Successfully!") + print("=" * 50) + print("โœ… All CRUD operations (Create, Read, Update, Delete) working") + print("โœ… SDK client properly configured and functional") + print("โœ… Real API integration successful") - finally: - if project_id: - try: - print(f"๐Ÿ—‘๏ธ Final cleanup: {project_id}") - projects.delete(project_id) - except Exception: - pass - - -def test_validation_integration(integration_client): - """Test validation functions work with real API - - Tests all validation scenarios with actual API calls - """ - projects, org = integration_client - - print("๐Ÿ” Testing validation with real API calls") - - try: - # Test valid project creation - unique_id = str(uuid.uuid4())[:8] - valid_name = f"validation-test-{unique_id}" - - print(f"โœ… Testing valid project creation: {valid_name}") - create_options = ProjectCreateOptions( - name=valid_name, description="Valid project" - ) - created_project = projects.create(org, create_options) - - assert created_project.name == valid_name - project_id = created_project.id - print(f"โœ… Valid project created successfully: {project_id}") - - # Test valid project update - updated_name = f"validation-updated-{unique_id}" - print(f"โœ… Testing valid project update: {updated_name}") - update_options = ProjectUpdateOptions(name=updated_name) - updated_project = projects.update(project_id, update_options) - - assert updated_project.name == updated_name - print("โœ… Valid project updated successfully") - - # Clean up - projects.delete(project_id) - print("โœ… Validation test cleanup completed") - - except Exception as e: - pytest.fail(f"Validation integration test failed: {e}") - - -def test_error_handling_integration(integration_client): - """Test error handling with real API calls - - Tests various error scenarios to ensure proper error handling - """ - projects, org = integration_client - - print("๐Ÿšซ Testing error handling scenarios") - - # Test reading a non-existent project - print("๐Ÿšซ Testing read non-existent project") - fake_project_id = "prj-nonexistent123456789" - try: - projects.read(fake_project_id) - pytest.fail("Should have raised an exception for non-existent project") except Exception as e: - print( - f"โœ… Correctly handled error for non-existent project: {type(e).__name__}" - ) - assert "404" in str(e) or "not found" in str(e).lower() - - # Test updating a non-existent project - print("๐Ÿšซ Testing update non-existent project") - try: - update_options = ProjectUpdateOptions(name="should-fail") - projects.update(fake_project_id, update_options) - pytest.fail("Should have raised an exception for non-existent project") - except Exception as e: - print( - f"โœ… Correctly handled update error for non-existent project: {type(e).__name__}" - ) - assert "404" in str(e) or "not found" in str(e).lower() - - # Test deleting a non-existent project - print("๐Ÿšซ Testing delete non-existent project") - try: - projects.delete(fake_project_id) - pytest.fail("Should have raised an exception for non-existent project") - except Exception as e: - print( - f"โœ… Correctly handled delete error for non-existent project: {type(e).__name__}" - ) - assert "404" in str(e) or "not found" in str(e).lower() - - print("โœ… All error handling scenarios tested successfully") - - -def test_project_tag_bindings_integration(integration_client): - """ - Integration test for project tag binding operations - - Note: Project tag bindings may not be available in all HCP Terraform plans. - This test gracefully handles unavailable features while testing what's available. - """ - projects, org = integration_client - - unique_id = str(uuid.uuid4())[:8] - test_name = f"tag-test-{unique_id}" - test_description = f"Project for testing tag bindings - {unique_id}" - project_id = None - - try: - # Create a test project for tagging operations - print(f"๐Ÿท๏ธ Setting up test project for tagging: {test_name}") - create_options = ProjectCreateOptions( - name=test_name, description=test_description - ) - created_project = projects.create(org, create_options) - project_id = created_project.id - print(f"โœ… Created test project: {project_id}") - - # Test 1: List tag bindings (this should work) - print("๐Ÿท๏ธ Testing LIST_TAG_BINDINGS") - try: - initial_tag_bindings = projects.list_tag_bindings(project_id) - assert isinstance(initial_tag_bindings, list), "Should return a list" - print(f"โœ… list_tag_bindings works: {len(initial_tag_bindings)} bindings") - list_tag_bindings_available = True - except Exception as e: - print(f"โŒ list_tag_bindings not available: {e}") - list_tag_bindings_available = False - - # Test 2: List effective tag bindings - print("๐Ÿท๏ธ Testing LIST_EFFECTIVE_TAG_BINDINGS") - try: - effective_bindings = projects.list_effective_tag_bindings(project_id) - assert isinstance(effective_bindings, list), "Should return a list" - print( - f"โœ… list_effective_tag_bindings works: {len(effective_bindings)} bindings" - ) - effective_tag_bindings_available = True - except Exception as e: - print(f"โŒ list_effective_tag_bindings not available: {e}") - print(" This feature may require a higher HCP Terraform plan") - effective_tag_bindings_available = False - - # Test 3: Add tag bindings (if basic listing works) - if list_tag_bindings_available: - print("๐Ÿท๏ธ Testing ADD_TAG_BINDINGS") - try: - test_tags = [ - TagBinding(key="environment", value="testing"), - TagBinding(key="integration-test", value="true"), - ] - add_options = ProjectAddTagBindingsOptions(tag_bindings=test_tags) - added_bindings = projects.add_tag_bindings(project_id, add_options) - - assert isinstance(added_bindings, list), "Should return a list" - assert len(added_bindings) == len(test_tags), ( - "Should return all added tags" - ) - print( - f"โœ… add_tag_bindings works: added {len(added_bindings)} bindings" - ) - - # Verify tags were actually added - current_bindings = projects.list_tag_bindings(project_id) - added_keys = {binding.key for binding in current_bindings} - for tag in test_tags: - assert tag.key in added_keys, ( - f"Tag {tag.key} not found after adding" - ) - print(f"โœ… Verified tags added: {len(current_bindings)} total bindings") - - add_tag_bindings_available = True - - # Test 4: Delete tag bindings - print("๐Ÿท๏ธ Testing DELETE_TAG_BINDINGS") - try: - result = projects.delete_tag_bindings(project_id) - assert result is None, "Delete should return None" - - # Verify deletion - final_bindings = projects.list_tag_bindings(project_id) - print( - f"โœ… delete_tag_bindings works: {len(final_bindings)} bindings remain" - ) - delete_tag_bindings_available = True - except Exception as e: - print(f"โŒ delete_tag_bindings not available: {e}") - delete_tag_bindings_available = False - - except Exception as e: - print(f"โŒ add_tag_bindings not available: {e}") - print(" This feature may require a higher HCP Terraform plan") - add_tag_bindings_available = False - delete_tag_bindings_available = False - else: - add_tag_bindings_available = False - delete_tag_bindings_available = False - - # Summary - print("\n๐Ÿ“Š Project Tag Bindings API Availability Summary:") - features = [ - ("list_tag_bindings", list_tag_bindings_available), - ("list_effective_tag_bindings", effective_tag_bindings_available), - ("add_tag_bindings", add_tag_bindings_available), - ("delete_tag_bindings", delete_tag_bindings_available), - ] - - for feature_name, available in features: - status = "โœ… Available" if available else "โŒ Not Available" - print(f" {feature_name}: {status}") - - available_count = sum(available for _, available in features) - print( - f"\n๐ŸŽฏ {available_count}/4 tag binding features are available in this HCP Terraform organization" - ) - - if available_count == 4: - print("๐ŸŽ‰ All project tag binding operations work perfectly!") - elif available_count > 0: - print("โœ… Partial functionality available - basic operations work!") - else: - print("โš ๏ธ Tag binding features may require a higher HCP Terraform plan") - - except Exception as e: - pytest.fail( - f"Project tag binding integration test failed unexpectedly. " - f"This may indicate a configuration or connectivity issue. Error: {e}" - ) + print(f"\nโŒ Example failed: {e}") + print("๐Ÿ”ง Check your TFE_TOKEN and TFE_ORG environment variables") + print("๐Ÿ”ง Ensure your token has proper permissions for project operations") finally: - # Clean up: Delete the test project + # Emergency cleanup if something went wrong if project_id: try: - print(f"๐Ÿงน Cleaning up test project: {project_id}") - projects.delete(project_id) - print("โœ… Test project deleted successfully") + print(f"\n๐Ÿงน Emergency cleanup: Deleting project {project_id}") + client.projects.delete(project_id) + print("โœ… Emergency cleanup successful") except Exception as cleanup_error: print( - f"โš ๏ธ Warning: Failed to clean up test project {project_id}: {cleanup_error}" + f"โš ๏ธ Warning: Failed to clean up project {project_id}: {cleanup_error}" ) -def test_project_tag_bindings_error_scenarios(integration_client): - """ - Test error handling for project tag binding operations - - Tests various error conditions: - - Invalid project IDs - - Empty tag binding lists - - Non-existent projects - """ - projects, org = integration_client +def demonstrate_error_handling(): + """Demonstrate proper error handling with the SDK.""" - print("๐Ÿท๏ธ Testing tag binding error scenarios") + print("\n๐Ÿšซ Error Handling Demonstration") + print("-" * 30) - # Test invalid project ID validation - print("๐Ÿšซ Testing invalid project ID scenarios") + token = os.getenv("TFE_TOKEN") + org_name = os.getenv("TFE_ORG") - invalid_project_ids = ["", "x", "invalid-id", None] - - for invalid_id in invalid_project_ids: - if invalid_id is None: - continue # Skip None as it will cause different error - - try: - projects.list_tag_bindings(invalid_id) - pytest.fail( - f"Should have raised ValueError for invalid project ID: {invalid_id}" - ) - except ValueError as e: - print(f"โœ… Correctly rejected invalid project ID '{invalid_id}': {e}") - assert "Project ID is required and must be valid" in str(e) + if not token or not org_name: + print("โŒ Skipping error handling demo - environment variables not set") + return - try: - projects.list_effective_tag_bindings(invalid_id) - pytest.fail( - f"Should have raised ValueError for invalid project ID: {invalid_id}" - ) - except ValueError as e: - print(f"โœ… Correctly rejected invalid project ID '{invalid_id}': {e}") + client = TFEClient() - try: - projects.delete_tag_bindings(invalid_id) - pytest.fail( - f"Should have raised ValueError for invalid project ID: {invalid_id}" - ) - except ValueError as e: - print(f"โœ… Correctly rejected invalid project ID '{invalid_id}': {e}") - - # Test empty tag binding list - print("๐Ÿšซ Testing empty tag binding list") - try: - fake_project_id = "prj-fakefakefake123" - empty_options = ProjectAddTagBindingsOptions(tag_bindings=[]) - projects.add_tag_bindings(fake_project_id, empty_options) - pytest.fail("Should have raised ValueError for empty tag binding list") - except ValueError as e: - print(f"โœ… Correctly rejected empty tag binding list: {e}") - assert "At least one tag binding is required" in str(e) - - # Test non-existent project operations - print("๐Ÿšซ Testing operations on non-existent project") - fake_project_id = "prj-doesnotexist123" - - # These should raise HTTP errors (404) from the API - for operation_name, operation_func in [ - ("list_tag_bindings", lambda: projects.list_tag_bindings(fake_project_id)), - ( - "list_effective_tag_bindings", - lambda: projects.list_effective_tag_bindings(fake_project_id), - ), - ("delete_tag_bindings", lambda: projects.delete_tag_bindings(fake_project_id)), - ]: - try: - operation_func() - pytest.fail(f"{operation_name} should have failed for non-existent project") - except Exception as e: - print( - f"โœ… {operation_name} correctly failed for non-existent project: {type(e).__name__}" - ) - # Should be some kind of HTTP error (404, not found, etc.) - assert ( - "404" in str(e) - or "not found" in str(e).lower() - or "does not exist" in str(e).lower() - ) - - # Test add_tag_bindings on non-existent project + # Test reading a non-existent project + print("๐Ÿ” Testing error handling for non-existent project...") try: - test_tags = [TagBinding(key="test", value="value")] - add_options = ProjectAddTagBindingsOptions(tag_bindings=test_tags) - projects.add_tag_bindings(fake_project_id, add_options) - pytest.fail("add_tag_bindings should have failed for non-existent project") + fake_project_id = "prj-nonexistent123456789" + client.projects.read(fake_project_id) + print("โŒ Unexpected: Should have failed for non-existent project") except Exception as e: - print( - f"โœ… add_tag_bindings correctly failed for non-existent project: {type(e).__name__}" - ) - assert ( - "404" in str(e) - or "not found" in str(e).lower() - or "does not exist" in str(e).lower() - ) + print(f"โœ… Correctly handled error: {type(e).__name__}") + print(f" Message: {str(e)[:100]}...") - print("โœ… All tag binding error scenarios tested successfully") + print("โœ… Error handling demonstration complete") -if __name__ == "__main__": - """ - You can also run this file directly for quick testing: +def main(): + """Main function to run all examples.""" + print("๐Ÿงช TFE Python SDK Project Examples") + print("This demonstrates real SDK usage with actual API calls\n") - export TFE_TOKEN="your-token" - export TFE_ORG="your-org" - python examples/integration_test_example.py - """ - import sys + # Run main project operations example + project_sdk_example() - token = os.environ.get("TFE_TOKEN") - org = os.environ.get("TFE_ORG") + # Run error handling demonstration + demonstrate_error_handling() - if not token or not org: - print("โŒ Please set TFE_TOKEN and TFE_ORG environment variables") - print(" export TFE_TOKEN='your-hcp-terraform-token'") - print(" export TFE_ORG='your-organization-name'") - sys.exit(1) - - print("๐Ÿงช Running integration tests directly...") - print( - " For full pytest features, use: pytest examples/integration_test_example.py -v -s" - ) - # Simple direct execution - pytest.main([__file__, "-v", "-s"]) +if __name__ == "__main__": + main() diff --git a/examples/variable_sets_example.py b/examples/variable_set.py similarity index 99% rename from examples/variable_sets_example.py rename to examples/variable_set.py index df621026..36c81682 100644 --- a/examples/variable_sets_example.py +++ b/examples/variable_set.py @@ -16,10 +16,8 @@ import os from tfe import TFEClient, TFEConfig -from tfe.types import ( - CategoryType, +from tfe.models import ( Parent, - Project, VariableSetApplyToProjectsOptions, VariableSetApplyToWorkspacesOptions, VariableSetCreateOptions, @@ -31,6 +29,10 @@ VariableSetVariableCreateOptions, VariableSetVariableListOptions, VariableSetVariableUpdateOptions, +) +from tfe.models.project import Project +from tfe.types import ( + CategoryType, Workspace, ) @@ -271,7 +273,7 @@ def variable_set_example(): # 9. Read the variable set with includes print("9. Reading variable set with includes...") - from tfe.types import VariableSetReadOptions + from tfe.models import VariableSetReadOptions read_options = VariableSetReadOptions( include=[VariableSetIncludeOpt.VARS, VariableSetIncludeOpt.WORKSPACES] diff --git a/src/tfe/models/__init__.py b/src/tfe/models/__init__.py index 865b9e92..1a3c6283 100644 --- a/src/tfe/models/__init__.py +++ b/src/tfe/models/__init__.py @@ -18,6 +18,15 @@ IngressAttributes, ) +# Re-export all project types +from .project import ( + Project, + ProjectAddTagBindingsOptions, + ProjectCreateOptions, + ProjectListOptions, + ProjectUpdateOptions, +) + # Re-export all registry module types from .registry_module_types import ( AgentExecutionMode, @@ -63,6 +72,26 @@ RegistryProviderReadOptions, ) +# Re-export all variable set types +from .variable_set import ( + Parent, + VariableSet, + VariableSetApplyToProjectsOptions, + VariableSetApplyToWorkspacesOptions, + VariableSetCreateOptions, + VariableSetIncludeOpt, + VariableSetListOptions, + VariableSetReadOptions, + VariableSetRemoveFromProjectsOptions, + VariableSetRemoveFromWorkspacesOptions, + VariableSetUpdateOptions, + VariableSetUpdateWorkspacesOptions, + VariableSetVariable, + VariableSetVariableCreateOptions, + VariableSetVariableListOptions, + VariableSetVariableUpdateOptions, +) + # Define what should be available when importing with * __all__ = [ # Configuration version types @@ -76,6 +105,12 @@ "ConfigurationVersionUpload", "ConfigVerIncludeOpt", "IngressAttributes", + # Project types + "Project", + "ProjectAddTagBindingsOptions", + "ProjectCreateOptions", + "ProjectListOptions", + "ProjectUpdateOptions", # Registry module types "AgentExecutionMode", "Commit", @@ -115,6 +150,29 @@ "RegistryProviderListOptions", "RegistryProviderPermissions", "RegistryProviderReadOptions", + # Project types + "Project", + "ProjectAddTagBindingsOptions", + "ProjectCreateOptions", + "ProjectListOptions", + "ProjectUpdateOptions", + # Variable set types + "Parent", + "VariableSet", + "VariableSetApplyToProjectsOptions", + "VariableSetApplyToWorkspacesOptions", + "VariableSetCreateOptions", + "VariableSetIncludeOpt", + "VariableSetListOptions", + "VariableSetReadOptions", + "VariableSetRemoveFromProjectsOptions", + "VariableSetRemoveFromWorkspacesOptions", + "VariableSetUpdateOptions", + "VariableSetUpdateWorkspacesOptions", + "VariableSetVariable", + "VariableSetVariableCreateOptions", + "VariableSetVariableListOptions", + "VariableSetVariableUpdateOptions", # Main types from types.py (will be dynamically added below) "Capacity", "DataRetentionPolicy", @@ -132,7 +190,6 @@ "OrganizationCreateOptions", "OrganizationUpdateOptions", "Pagination", - "Project", "ReadRunQueueOptions", "Run", "RunQueue", diff --git a/src/tfe/models/project.py b/src/tfe/models/project.py new file mode 100644 index 00000000..84f5ee08 --- /dev/null +++ b/src/tfe/models/project.py @@ -0,0 +1,62 @@ +"""Project-related types for the Terraform Cloud/Enterprise API. + +This module contains all Pydantic models and types related to projects, +including the main Project model and all project-related option classes. +""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, Field + + +class Project(BaseModel): + """Project represents a Terraform Enterprise project""" + + id: str + name: str | None = None + description: str = "" + organization: str | None = None + created_at: str = "" + updated_at: str = "" + workspace_count: int = 0 + default_execution_mode: str = "remote" + + +class ProjectListOptions(BaseModel): + """Options for listing projects""" + + # Optional: String used to filter results by complete project name + name: str | None = None + # Optional: Query string to search projects by names + query: str | None = None + # Optional: Include related resources + include: list[str] | None = None + # Pagination options + page_number: int | None = None + page_size: int | None = None + + +class ProjectCreateOptions(BaseModel): + """Options for creating a project""" + + # Required: A name to identify the project + name: str + # Optional: A description for the project + description: str | None = None + + +class ProjectUpdateOptions(BaseModel): + """Options for updating a project""" + + # Optional: A name to identify the project + name: str | None = None + # Optional: A description for the project + description: str | None = None + + +class ProjectAddTagBindingsOptions(BaseModel): + """Options for adding tag bindings to a project""" + + tag_bindings: list[Any] = Field(default_factory=list) diff --git a/src/tfe/models/variable_set.py b/src/tfe/models/variable_set.py new file mode 100644 index 00000000..f141a988 --- /dev/null +++ b/src/tfe/models/variable_set.py @@ -0,0 +1,162 @@ +"""Variable set type definitions for the TFE API.""" + +from __future__ import annotations + +from datetime import datetime +from enum import Enum + +from pydantic import BaseModel, Field + +from ..types import CategoryType, Organization, Workspace +from .project import Project + + +class VariableSetIncludeOpt(str, Enum): + """Include options for variable set operations.""" + + WORKSPACES = "workspaces" + PROJECTS = "projects" + VARS = "vars" + CURRENT_RUN = "current-run" + + +class Parent(BaseModel): + """Parent represents the variable set's parent (organizations and projects are supported).""" + + organization: Organization | None = None + project: Project | None = None + + +class VariableSet(BaseModel): + """Represents a Terraform Enterprise variable set.""" + + id: str | None = None + name: str | None = None + description: str | None = None + global_: bool | None = Field(default=None, alias="global") + priority: bool | None = None + created_at: datetime | None = None + updated_at: datetime | None = None + + # Relations + organization: Organization | None = None + workspaces: list[Workspace] = Field(default_factory=list) + projects: list[Project] = Field(default_factory=list) + vars: list[VariableSetVariable] = Field(default_factory=list) + parent: Parent | None = None + + +class VariableSetVariable(BaseModel): + """Represents a variable within a variable set.""" + + id: str | None = None + key: str + value: str | None = None + description: str | None = None + category: CategoryType + hcl: bool | None = None + sensitive: bool | None = None + version_id: str | None = None + + # Relations + variable_set: VariableSet | None = None + + +# Variable Set Options + + +class VariableSetListOptions(BaseModel): + """Options for listing variable sets.""" + + # Pagination options + page_number: int | None = None + page_size: int | None = None + include: list[VariableSetIncludeOpt] | None = None + query: str | None = None # Filter by name + + +class VariableSetCreateOptions(BaseModel): + """Options for creating a variable set.""" + + name: str + description: str | None = None + global_: bool = Field(alias="global") + priority: bool | None = None + parent: Parent | None = None + + +class VariableSetReadOptions(BaseModel): + """Options for reading a variable set.""" + + include: list[VariableSetIncludeOpt] | None = None + + +class VariableSetUpdateOptions(BaseModel): + """Options for updating a variable set.""" + + name: str | None = None + description: str | None = None + global_: bool | None = Field(alias="global", default=None) + priority: bool | None = None + + +class VariableSetApplyToWorkspacesOptions(BaseModel): + """Options for applying a variable set to workspaces.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +class VariableSetRemoveFromWorkspacesOptions(BaseModel): + """Options for removing a variable set from workspaces.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +class VariableSetApplyToProjectsOptions(BaseModel): + """Options for applying a variable set to projects.""" + + projects: list[Project] = Field(default_factory=list) + + +class VariableSetRemoveFromProjectsOptions(BaseModel): + """Options for removing a variable set from projects.""" + + projects: list[Project] = Field(default_factory=list) + + +class VariableSetUpdateWorkspacesOptions(BaseModel): + """Options for updating workspaces associated with a variable set.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +# Variable Set Variable Options + + +class VariableSetVariableListOptions(BaseModel): + """Options for listing variables in a variable set.""" + + # Pagination options + page_number: int | None = None + page_size: int | None = None + + +class VariableSetVariableCreateOptions(BaseModel): + """Options for creating a variable in a variable set.""" + + key: str + value: str | None = None + description: str | None = None + category: CategoryType + hcl: bool | None = None + sensitive: bool | None = None + + +class VariableSetVariableUpdateOptions(BaseModel): + """Options for updating a variable in a variable set.""" + + key: str | None = None + value: str | None = None + description: str | None = None + hcl: bool | None = None + sensitive: bool | None = None diff --git a/src/tfe/models/variable_set_types.py b/src/tfe/models/variable_set_types.py new file mode 100644 index 00000000..f1894102 --- /dev/null +++ b/src/tfe/models/variable_set_types.py @@ -0,0 +1,165 @@ +"""Variable set type definitions for the TFE API.""" + +from __future__ import annotations + +from datetime import datetime +from enum import Enum + +from pydantic import BaseModel, Field + +from ..types import CategoryType, Organization, Workspace +from .project import Project + + +class VariableSetIncludeOpt(str, Enum): + """Include options for variable set operations.""" + + WORKSPACES = "workspaces" + PROJECTS = "projects" + VARS = "vars" + CURRENT_RUN = "current-run" + + +class Parent(BaseModel): + """Parent represents the variable set's parent (organizations and projects are supported).""" + + organization: Organization | None = None + project: Project | None = None + + +class VariableSet(BaseModel): + """Represents a Terraform Enterprise variable set.""" + + id: str | None = None + name: str | None = None + description: str | None = None + global_: bool | None = Field(default=None, alias="global") + priority: bool | None = None + created_at: datetime | None = None + updated_at: datetime | None = None + + # Relations + organization: Organization | None = None + workspaces: list[Workspace] = Field(default_factory=list) + projects: list[Project] = Field(default_factory=list) + vars: list[VariableSetVariable] = Field(default_factory=list) + parent: Parent | None = None + + +class VariableSetVariable(BaseModel): + """Represents a variable within a variable set.""" + + id: str | None = None + key: str + value: str | None = None + description: str | None = None + category: CategoryType + hcl: bool | None = None + sensitive: bool | None = None + version_id: str | None = None + + # Relations + variable_set: VariableSet | None = None + + +# Variable Set Options + + +class VariableSetListOptions(BaseModel): + """Options for listing variable sets.""" + + # Pagination options + page_number: int | None = None + page_size: int | None = None + include: list[VariableSetIncludeOpt] | None = None + query: str | None = None # Filter by name + + +class VariableSetCreateOptions(BaseModel): + """Options for creating a variable set.""" + + name: str + description: str | None = None + global_: bool = Field(alias="global") + priority: bool | None = None + parent: Parent | None = None + + +class VariableSetReadOptions(BaseModel): + """Options for reading a variable set.""" + + include: list[VariableSetIncludeOpt] | None = None + + +class VariableSetUpdateOptions(BaseModel): + """Options for updating a variable set.""" + + name: str | None = None + description: str | None = None + global_: bool | None = Field(alias="global", default=None) + priority: bool | None = None + + +class VariableSetApplyToWorkspacesOptions(BaseModel): + """Options for applying a variable set to workspaces.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +class VariableSetRemoveFromWorkspacesOptions(BaseModel): + """Options for removing a variable set from workspaces.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +class VariableSetApplyToProjectsOptions(BaseModel): + """Options for applying a variable set to projects.""" + + projects: list[Project] = Field(default_factory=list) + + +class VariableSetRemoveFromProjectsOptions(BaseModel): + """Options for removing a variable set from projects.""" + + projects: list[Project] = Field(default_factory=list) + + +class VariableSetUpdateWorkspacesOptions(BaseModel): + """Options for updating workspaces associated with a variable set.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +# Variable Set Variable Options + + +class VariableSetVariableListOptions(BaseModel): + """Options for listing variables in a variable set.""" + + # Pagination options + page_number: int | None = None + page_size: int | None = None + + +class VariableSetVariableCreateOptions(BaseModel): + """Options for creating a variable in a variable set.""" + + key: str + value: str | None = None + description: str | None = None + category: CategoryType + hcl: bool | None = None + sensitive: bool | None = None + + +class VariableSetVariableUpdateOptions(BaseModel): + """Options for updating a variable in a variable set.""" + + key: str | None = None + value: str | None = None + description: str | None = None + hcl: bool | None = None + sensitive: bool | None = None + + +# Model rebuild functionality for runtime diff --git a/src/tfe/resources/projects.py b/src/tfe/resources/projects.py index e6a35dc4..1d1bf7b7 100644 --- a/src/tfe/resources/projects.py +++ b/src/tfe/resources/projects.py @@ -5,13 +5,15 @@ from collections.abc import Iterator from typing import Any -from ..types import ( - EffectiveTagBinding, +from ..models.project import ( Project, ProjectAddTagBindingsOptions, ProjectCreateOptions, ProjectListOptions, ProjectUpdateOptions, +) +from ..types import ( + EffectiveTagBinding, TagBinding, ) from ..utils import valid_string, valid_string_id diff --git a/src/tfe/resources/variable_sets.py b/src/tfe/resources/variable_sets.py index bc0e0db7..48064856 100644 --- a/src/tfe/resources/variable_sets.py +++ b/src/tfe/resources/variable_sets.py @@ -4,8 +4,7 @@ from typing import Any from tfe._http import HTTPTransport -from tfe.resources._base import _Service -from tfe.types import ( +from tfe.models.variable_set import ( VariableSet, VariableSetApplyToProjectsOptions, VariableSetApplyToWorkspacesOptions, @@ -22,6 +21,7 @@ VariableSetVariableListOptions, VariableSetVariableUpdateOptions, ) +from tfe.resources._base import _Service class VariableSets(_Service): diff --git a/src/tfe/types.py b/src/tfe/types.py index bc115aaa..30c6246c 100644 --- a/src/tfe/types.py +++ b/src/tfe/types.py @@ -102,57 +102,6 @@ class Organization(BaseModel): data_retention_policy_choice: dict | None = None -class Project(BaseModel): - """Project represents a Terraform Enterprise project""" - - id: str - name: str | None = None - description: str = "" - organization: str | None = None - created_at: str = "" - updated_at: str = "" - workspace_count: int = 0 - default_execution_mode: str = "remote" - - -class ProjectListOptions(BaseModel): - """Options for listing projects""" - - # Optional: String used to filter results by complete project name - name: str | None = None - # Optional: Query string to search projects by names - query: str | None = None - # Optional: Include related resources - include: list[str] | None = None - # Pagination options - page_number: int | None = None - page_size: int | None = None - - -class ProjectCreateOptions(BaseModel): - """Options for creating a project""" - - # Required: A name to identify the project - name: str - # Optional: A description for the project - description: str | None = None - - -class ProjectUpdateOptions(BaseModel): - """Options for updating a project""" - - # Optional: A name to identify the project - name: str | None = None - # Optional: A description for the project - description: str | None = None - - -class ProjectAddTagBindingsOptions(BaseModel): - """Options for adding tag bindings to a project""" - - tag_bindings: list[TagBinding] = Field(default_factory=list) - - class Workspace(BaseModel): id: str name: str | None = None @@ -205,7 +154,7 @@ class Workspace(BaseModel): agent_pool: Any | None = None # AgentPool object current_run: Any | None = None # Run object current_state_version: Any | None = None # StateVersion object - project: Project | None = None + project: Any | None = None # Project object ssh_key: Any | None = None # SSHKey object outputs: list[WorkspaceOutputs] = Field(default_factory=list) tags: list[Tag] = Field(default_factory=list) @@ -532,7 +481,7 @@ class WorkspaceCreateOptions(BaseModel): hyok_enabled: bool | None = None tags: list[Tag] = Field(default_factory=list) setting_overwrites: WorkspaceSettingOverwrites | None = None - project: Project | None = None + project: Any | None = None # Project object tag_bindings: list[TagBinding] = Field(default_factory=list) @@ -562,7 +511,7 @@ class WorkspaceUpdateOptions(BaseModel): working_directory: str | None = None hyok_enabled: bool | None = None setting_overwrites: WorkspaceSettingOverwrites | None = None - project: Project | None = None + project: Any | None = None # Project object tag_bindings: list[TagBinding] = Field(default_factory=list) @@ -660,154 +609,3 @@ class WorkspaceAddTagBindingsOptions(BaseModel): # Variable Set related types - - -class VariableSetIncludeOpt(str, Enum): - """Include options for variable set operations.""" - - WORKSPACES = "workspaces" - PROJECTS = "projects" - VARS = "vars" - CURRENT_RUN = "current-run" - - -class Parent(BaseModel): - """Parent represents the variable set's parent (organizations and projects are supported).""" - - organization: Organization | None = None - project: Project | None = None - - -class VariableSet(BaseModel): - """Represents a Terraform Enterprise variable set.""" - - id: str | None = None - name: str | None = None - description: str | None = None - global_: bool | None = Field(default=None, alias="global") - priority: bool | None = None - created_at: datetime | None = None - updated_at: datetime | None = None - - # Relations - organization: Organization | None = None - workspaces: list[Workspace] = Field(default_factory=list) - projects: list[Project] = Field(default_factory=list) - vars: list[VariableSetVariable] = Field(default_factory=list) - parent: Parent | None = None - - -class VariableSetVariable(BaseModel): - """Represents a variable within a variable set.""" - - id: str | None = None - key: str - value: str | None = None - description: str | None = None - category: CategoryType - hcl: bool | None = None - sensitive: bool | None = None - version_id: str | None = None - - # Relations - variable_set: VariableSet | None = None - - -# Variable Set Options - - -class VariableSetListOptions(BaseModel): - """Options for listing variable sets.""" - - # Pagination options - page_number: int | None = None - page_size: int | None = None - include: list[VariableSetIncludeOpt] | None = None - query: str | None = None # Filter by name - - -class VariableSetCreateOptions(BaseModel): - """Options for creating a variable set.""" - - name: str - description: str | None = None - global_: bool = Field(alias="global") - priority: bool | None = None - parent: Parent | None = None - - -class VariableSetReadOptions(BaseModel): - """Options for reading a variable set.""" - - include: list[VariableSetIncludeOpt] | None = None - - -class VariableSetUpdateOptions(BaseModel): - """Options for updating a variable set.""" - - name: str | None = None - description: str | None = None - global_: bool | None = Field(alias="global", default=None) - priority: bool | None = None - - -class VariableSetApplyToWorkspacesOptions(BaseModel): - """Options for applying a variable set to workspaces.""" - - workspaces: list[Workspace] = Field(default_factory=list) - - -class VariableSetRemoveFromWorkspacesOptions(BaseModel): - """Options for removing a variable set from workspaces.""" - - workspaces: list[Workspace] = Field(default_factory=list) - - -class VariableSetApplyToProjectsOptions(BaseModel): - """Options for applying a variable set to projects.""" - - projects: list[Project] = Field(default_factory=list) - - -class VariableSetRemoveFromProjectsOptions(BaseModel): - """Options for removing a variable set from projects.""" - - projects: list[Project] = Field(default_factory=list) - - -class VariableSetUpdateWorkspacesOptions(BaseModel): - """Options for updating workspaces associated with a variable set.""" - - workspaces: list[Workspace] = Field(default_factory=list) - - -# Variable Set Variable Options - - -class VariableSetVariableListOptions(BaseModel): - """Options for listing variables in a variable set.""" - - # Pagination options - page_number: int | None = None - page_size: int | None = None - - -class VariableSetVariableCreateOptions(BaseModel): - """Options for creating a variable in a variable set.""" - - key: str - value: str | None = None - description: str | None = None - category: CategoryType - hcl: bool | None = None - sensitive: bool | None = None - - -class VariableSetVariableUpdateOptions(BaseModel): - """Options for updating a variable in a variable set.""" - - key: str | None = None - value: str | None = None - description: str | None = None - hcl: bool | None = None - sensitive: bool | None = None diff --git a/tests/units/test_project.py b/tests/units/test_project.py index 7876f748..ad8fe43e 100644 --- a/tests/units/test_project.py +++ b/tests/units/test_project.py @@ -1,12 +1,14 @@ from unittest.mock import Mock -from tfe.resources.projects import Projects, _safe_str -from tfe.types import ( - EffectiveTagBinding, +from tfe.models.project import ( Project, ProjectAddTagBindingsOptions, ProjectCreateOptions, ProjectUpdateOptions, +) +from tfe.resources.projects import Projects, _safe_str +from tfe.types import ( + EffectiveTagBinding, TagBinding, ) diff --git a/tests/units/test_variable_sets.py b/tests/units/test_variable_sets.py index 77a672d5..735204fa 100644 --- a/tests/units/test_variable_sets.py +++ b/tests/units/test_variable_sets.py @@ -4,9 +4,7 @@ import pytest -from tfe.resources.variable_sets import VariableSets, VariableSetVariables -from tfe.types import ( - CategoryType, +from tfe.models import ( Parent, Project, VariableSet, @@ -23,6 +21,10 @@ VariableSetVariable, VariableSetVariableCreateOptions, VariableSetVariableUpdateOptions, +) +from tfe.resources.variable_sets import VariableSets, VariableSetVariables +from tfe.types import ( + CategoryType, Workspace, ) diff --git a/tests/units/test_workspaces.py b/tests/units/test_workspaces.py index 29c838ca..723e1a89 100644 --- a/tests/units/test_workspaces.py +++ b/tests/units/test_workspaces.py @@ -19,6 +19,7 @@ RequiredSSHKeyIDError, WorkspaceMinimumLimitError, ) +from src.tfe.models.project import Project from src.tfe.resources.workspaces import Workspaces, _ws_from from src.tfe.types import ( DataRetentionPolicyDeleteOlderSetOptions, @@ -26,7 +27,6 @@ DataRetentionPolicySetOptions, EffectiveTagBinding, ExecutionMode, - Project, Tag, TagBinding, VCSRepo,