From 4fbfd1301bcf33ae39f44ce72e3b83881897d66a Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Wed, 8 Oct 2025 18:00:29 +0530 Subject: [PATCH 1/9] Rename package to pytfe and other fixes --- examples/agent.py | 12 +-- examples/agent_pool.py | 9 +- examples/apply.py | 2 +- ...plete_test.py => configuration_version.py} | 6 +- ...lient_complete_test.py => oauth_client.py} | 8 +- ..._token_complete_test.py => oauth_token.py} | 8 +- examples/org.py | 4 +- examples/plan.py | 2 +- examples/policy.py | 6 +- examples/project.py | 10 +-- examples/query_run.py | 4 +- ...le_complete_test.py => registry_module.py} | 8 +- ...der_individual.py => registry_provider.py} | 4 +- examples/reserved_tag_key.py | 6 +- examples/run.py | 6 +- examples/run_events.py | 4 +- examples/run_task.py | 4 +- examples/run_trigger.py | 6 +- examples/ssh_keys.py | 6 +- examples/state_versions.py | 8 +- ...iable_sets_example.py => variable_sets.py} | 8 +- examples/variables.py | 4 +- examples/workspace.py | 8 +- pyproject.toml | 12 +-- src/{tfe => pytfe}/__init__.py | 0 src/{tfe => pytfe}/_http.py | 46 ---------- src/{tfe => pytfe}/_jsonapi.py | 2 +- src/{tfe => pytfe}/client.py | 5 +- src/{tfe => pytfe}/config.py | 0 src/{tfe => pytfe}/errors.py | 0 src/{tfe => pytfe}/models/__init__.py | 0 src/{tfe => pytfe}/models/agent.py | 0 src/{tfe => pytfe}/models/apply.py | 0 src/{tfe => pytfe}/models/comment.py | 0 .../models/configuration_version.py | 0 .../models/configuration_version_types.py | 0 src/{tfe => pytfe}/models/cost_estimate.py | 0 src/{tfe => pytfe}/models/oauth_client.py | 0 src/{tfe => pytfe}/models/oauth_token.py | 0 src/{tfe => pytfe}/models/organization.py | 0 src/{tfe => pytfe}/models/plan.py | 0 src/{tfe => pytfe}/models/plan_export.py | 0 src/{tfe => pytfe}/models/policy.py | 0 src/{tfe => pytfe}/models/policy_check.py | 0 src/{tfe => pytfe}/models/policy_set.py | 0 src/{tfe => pytfe}/models/query_run.py | 0 .../models/registry_module_types.py | 0 .../models/registry_provider_types.py | 0 src/{tfe => pytfe}/models/reserved_tag_key.py | 0 src/{tfe => pytfe}/models/run.py | 0 src/{tfe => pytfe}/models/run_event.py | 0 src/{tfe => pytfe}/models/run_task.py | 0 src/{tfe => pytfe}/models/run_trigger.py | 0 src/{tfe => pytfe}/models/ssh_key.py | 0 src/{tfe => pytfe}/models/state_version.py | 0 .../models/state_version_output.py | 0 src/{tfe => pytfe}/models/task_stage.py | 0 src/{tfe => pytfe}/models/user.py | 0 src/{tfe => pytfe}/models/workspace.py | 0 .../models/workspace_run_task.py | 0 src/{tfe => pytfe}/resources/_base.py | 27 ------ .../resources/admin/settings.py | 0 src/{tfe => pytfe}/resources/agent_pools.py | 0 src/{tfe => pytfe}/resources/agents.py | 0 src/{tfe => pytfe}/resources/apply.py | 0 .../resources/configuration_version.py | 0 src/{tfe => pytfe}/resources/oauth_client.py | 0 src/{tfe => pytfe}/resources/oauth_token.py | 0 src/{tfe => pytfe}/resources/organizations.py | 0 src/{tfe => pytfe}/resources/plan.py | 0 src/{tfe => pytfe}/resources/policy.py | 0 src/{tfe => pytfe}/resources/projects.py | 0 src/{tfe => pytfe}/resources/query_run.py | 0 .../resources/registry_module.py | 0 .../resources/registry_provider.py | 0 .../resources/reserved_tag_key.py | 0 src/{tfe => pytfe}/resources/run.py | 0 src/{tfe => pytfe}/resources/run_event.py | 0 src/{tfe => pytfe}/resources/run_task.py | 0 src/{tfe => pytfe}/resources/run_trigger.py | 0 src/{tfe => pytfe}/resources/ssh_keys.py | 0 .../resources/state_version_outputs.py | 0 .../resources/state_versions.py | 0 src/{tfe => pytfe}/resources/variable.py | 0 src/{tfe => pytfe}/resources/variable_sets.py | 6 +- src/{tfe => pytfe}/resources/workspaces.py | 0 src/{tfe => pytfe}/types.py | 0 src/{tfe => pytfe}/utils.py | 68 +++++++++++++++ src/tfe/aclient.py | 27 ------ src/tfe/project.py | 83 ------------------- 90 files changed, 154 insertions(+), 265 deletions(-) rename examples/{configuration_version_complete_test.py => configuration_version.py} (99%) rename examples/{oauth_client_complete_test.py => oauth_client.py} (99%) rename examples/{oauth_token_complete_test.py => oauth_token.py} (97%) rename examples/{registry_module_complete_test.py => registry_module.py} (99%) rename examples/{registry_provider_individual.py => registry_provider.py} (99%) rename examples/{variable_sets_example.py => variable_sets.py} (99%) rename src/{tfe => pytfe}/__init__.py (100%) rename src/{tfe => pytfe}/_http.py (75%) rename src/{tfe => pytfe}/_jsonapi.py (95%) rename src/{tfe => pytfe}/client.py (97%) rename src/{tfe => pytfe}/config.py (100%) rename src/{tfe => pytfe}/errors.py (100%) rename src/{tfe => pytfe}/models/__init__.py (100%) rename src/{tfe => pytfe}/models/agent.py (100%) rename src/{tfe => pytfe}/models/apply.py (100%) rename src/{tfe => pytfe}/models/comment.py (100%) rename src/{tfe => pytfe}/models/configuration_version.py (100%) rename src/{tfe => pytfe}/models/configuration_version_types.py (100%) rename src/{tfe => pytfe}/models/cost_estimate.py (100%) rename src/{tfe => pytfe}/models/oauth_client.py (100%) rename src/{tfe => pytfe}/models/oauth_token.py (100%) rename src/{tfe => pytfe}/models/organization.py (100%) rename src/{tfe => pytfe}/models/plan.py (100%) rename src/{tfe => pytfe}/models/plan_export.py (100%) rename src/{tfe => pytfe}/models/policy.py (100%) rename src/{tfe => pytfe}/models/policy_check.py (100%) rename src/{tfe => pytfe}/models/policy_set.py (100%) rename src/{tfe => pytfe}/models/query_run.py (100%) rename src/{tfe => pytfe}/models/registry_module_types.py (100%) rename src/{tfe => pytfe}/models/registry_provider_types.py (100%) rename src/{tfe => pytfe}/models/reserved_tag_key.py (100%) rename src/{tfe => pytfe}/models/run.py (100%) rename src/{tfe => pytfe}/models/run_event.py (100%) rename src/{tfe => pytfe}/models/run_task.py (100%) rename src/{tfe => pytfe}/models/run_trigger.py (100%) rename src/{tfe => pytfe}/models/ssh_key.py (100%) rename src/{tfe => pytfe}/models/state_version.py (100%) rename src/{tfe => pytfe}/models/state_version_output.py (100%) rename src/{tfe => pytfe}/models/task_stage.py (100%) rename src/{tfe => pytfe}/models/user.py (100%) rename src/{tfe => pytfe}/models/workspace.py (100%) rename src/{tfe => pytfe}/models/workspace_run_task.py (100%) rename src/{tfe => pytfe}/resources/_base.py (55%) rename src/{tfe => pytfe}/resources/admin/settings.py (100%) rename src/{tfe => pytfe}/resources/agent_pools.py (100%) rename src/{tfe => pytfe}/resources/agents.py (100%) rename src/{tfe => pytfe}/resources/apply.py (100%) rename src/{tfe => pytfe}/resources/configuration_version.py (100%) rename src/{tfe => pytfe}/resources/oauth_client.py (100%) rename src/{tfe => pytfe}/resources/oauth_token.py (100%) rename src/{tfe => pytfe}/resources/organizations.py (100%) rename src/{tfe => pytfe}/resources/plan.py (100%) rename src/{tfe => pytfe}/resources/policy.py (100%) rename src/{tfe => pytfe}/resources/projects.py (100%) rename src/{tfe => pytfe}/resources/query_run.py (100%) rename src/{tfe => pytfe}/resources/registry_module.py (100%) rename src/{tfe => pytfe}/resources/registry_provider.py (100%) rename src/{tfe => pytfe}/resources/reserved_tag_key.py (100%) rename src/{tfe => pytfe}/resources/run.py (100%) rename src/{tfe => pytfe}/resources/run_event.py (100%) rename src/{tfe => pytfe}/resources/run_task.py (100%) rename src/{tfe => pytfe}/resources/run_trigger.py (100%) rename src/{tfe => pytfe}/resources/ssh_keys.py (100%) rename src/{tfe => pytfe}/resources/state_version_outputs.py (100%) rename src/{tfe => pytfe}/resources/state_versions.py (100%) rename src/{tfe => pytfe}/resources/variable.py (100%) rename src/{tfe => pytfe}/resources/variable_sets.py (99%) rename src/{tfe => pytfe}/resources/workspaces.py (100%) rename src/{tfe => pytfe}/types.py (100%) rename src/{tfe => pytfe}/utils.py (80%) delete mode 100644 src/tfe/aclient.py delete mode 100644 src/tfe/project.py diff --git a/examples/agent.py b/examples/agent.py index 01daf88c..1ba0a0a9 100644 --- a/examples/agent.py +++ b/examples/agent.py @@ -17,15 +17,15 @@ Usage: export TFE_TOKEN="your-token-here" export TFE_ORG="your-organization" - python examples/agent_simple.py + python examples/agent.py """ import os -from tfe.client import TFEClient -from tfe.config import TFEConfig -from tfe.errors import NotFound -from tfe.models.agent import AgentListOptions +from pytfe.client import TFEClient +from pytfe.config import TFEConfig +from pytfe.errors import NotFound +from pytfe.models.agent import AgentListOptions def main(): @@ -45,7 +45,7 @@ def main(): # Create TFE client config = TFEConfig(token=token, address=address) - client = TFEClient(config=config) + client = TFEClient(config) print(f"🔗 Connected to: {address}") print(f"🏢 Organization: {org}") diff --git a/examples/agent_pool.py b/examples/agent_pool.py index d9a37dfe..e347736a 100644 --- a/examples/agent_pool.py +++ b/examples/agent_pool.py @@ -14,15 +14,15 @@ Usage: export TFE_TOKEN="your-token-here" export TFE_ORG="your-organization" - python examples/agent_pool_simple.py + python examples/agent_pool.py """ import os import uuid -from tfe import TFEClient, TFEConfig -from tfe.errors import NotFound -from tfe.models.agent import ( +from pytfe import TFEClient, TFEConfig +from pytfe.errors import NotFound +from pytfe.models.agent import ( AgentPoolAllowedWorkspacePolicy, AgentPoolCreateOptions, AgentPoolListOptions, @@ -30,7 +30,6 @@ AgentTokenCreateOptions, ) - def main(): """Main function demonstrating agent pool operations.""" # Get environment variables diff --git a/examples/apply.py b/examples/apply.py index 218697d1..8fb9780d 100644 --- a/examples/apply.py +++ b/examples/apply.py @@ -3,7 +3,7 @@ import argparse import os -from tfe import TFEClient, TFEConfig +from pytfe import TFEClient, TFEConfig def _print_header(title: str): diff --git a/examples/configuration_version_complete_test.py b/examples/configuration_version.py similarity index 99% rename from examples/configuration_version_complete_test.py rename to examples/configuration_version.py index 3785b449..e983e947 100644 --- a/examples/configuration_version_complete_test.py +++ b/examples/configuration_version.py @@ -2,7 +2,7 @@ """ Complete Configuration Version Testing Suite -This file contains individual tests for all 12 configuration version functions implemented in src/tfe/resources/configuration_version.py: +This file contains individual tests for all 12 configuration version functions: CONFIGURATION VERSION FUNCTIONS AVAILABLE FOR TESTING: 1. list() - List configuration versions for a workspace @@ -35,8 +35,8 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient, TFEConfig -from tfe.models import ( +from pytfe import TFEClient, TFEConfig +from pytfe.models import ( ConfigurationVersionCreateOptions, ConfigurationVersionListOptions, ConfigurationVersionReadOptions, diff --git a/examples/oauth_client_complete_test.py b/examples/oauth_client.py similarity index 99% rename from examples/oauth_client_complete_test.py rename to examples/oauth_client.py index d09867a3..f449ea23 100644 --- a/examples/oauth_client_complete_test.py +++ b/examples/oauth_client.py @@ -2,7 +2,7 @@ """ Complete OAuth Client Testing Suite -This file contains individual tests for all 8 OAuth client functions implemented in src/tfe/resources/oauth_client.py: +This file contains individual tests for all 8 OAuth client functions: PUBLIC FUNCTIONS AVAILABLE FOR TESTING: 1. list() - List all OAuth clients for an organization @@ -35,9 +35,9 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient, TFEConfig -from tfe.errors import NotFound -from tfe.models.oauth_client import ( +from pytfe import TFEClient, TFEConfig +from pytfe.errors import NotFound +from pytfe.models.oauth_client import ( OAuthClientAddProjectsOptions, OAuthClientCreateOptions, OAuthClientIncludeOpt, diff --git a/examples/oauth_token_complete_test.py b/examples/oauth_token.py similarity index 97% rename from examples/oauth_token_complete_test.py rename to examples/oauth_token.py index fba05b86..70c6e798 100644 --- a/examples/oauth_token_complete_test.py +++ b/examples/oauth_token.py @@ -2,7 +2,7 @@ """ Complete OAuth Token Testing Suite -This file contains individual tests for all 4 OAuth token functions implemented in src/tfe/resources/oauth_token.py: +This file contains individual tests for all 4 OAuth token functions: FUNCTIONS AVAILABLE FOR TESTING: 1. list() - List OAuth tokens for an organization @@ -28,9 +28,9 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient, TFEConfig -from tfe.errors import NotFound -from tfe.models.oauth_token import OAuthTokenListOptions, OAuthTokenUpdateOptions +from pytfe import TFEClient, TFEConfig +from pytfe.errors import NotFound +from pytfe.models.oauth_token import OAuthTokenListOptions, OAuthTokenUpdateOptions def main(): diff --git a/examples/org.py b/examples/org.py index 76364507..82475de4 100644 --- a/examples/org.py +++ b/examples/org.py @@ -1,5 +1,5 @@ -from tfe import TFEClient, TFEConfig -from tfe.types import ( +from pytfe import TFEClient, TFEConfig +from pytfe.types import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, OrganizationCreateOptions, diff --git a/examples/plan.py b/examples/plan.py index ec36269a..41b1e779 100644 --- a/examples/plan.py +++ b/examples/plan.py @@ -4,7 +4,7 @@ import json import os -from tfe import TFEClient, TFEConfig +from pytfe import TFEClient, TFEConfig def _print_header(title: str): diff --git a/examples/policy.py b/examples/policy.py index 55cfcf7f..46314a39 100644 --- a/examples/policy.py +++ b/examples/policy.py @@ -22,14 +22,14 @@ import os from pathlib import Path -from tfe import TFEClient, TFEConfig -from tfe.models.policy import ( +from pytfe import TFEClient, TFEConfig +from pytfe.models.policy import ( EnforcementLevel, PolicyCreateOptions, PolicyListOptions, PolicyUpdateOptions, ) -from tfe.models.policy_set import PolicyKind +from pytfe.models.policy_set import PolicyKind def _print_header(title: str): diff --git a/examples/project.py b/examples/project.py index 3866365f..2d335848 100644 --- a/examples/project.py +++ b/examples/project.py @@ -1,7 +1,7 @@ """ Comprehensive Integration Test for python-tfe Projects CRUD Operations -This file tests all CRUD operations from src/tfe/resources/projects.py: +This file tests all CRUD operations: - List: Get all projects in an organization - Create: Add new projects with validation - Read: Get specific project details @@ -29,10 +29,10 @@ import pytest -from tfe._http import HTTPTransport -from tfe.config import TFEConfig -from tfe.resources.projects import Projects -from tfe.types import ( +from pytfe._http import HTTPTransport +from pytfe.config import TFEConfig +from pytfe.resources.projects import Projects +from pytfe.types import ( ProjectAddTagBindingsOptions, ProjectCreateOptions, ProjectListOptions, diff --git a/examples/query_run.py b/examples/query_run.py index 0b3e4e65..f3ec6829 100644 --- a/examples/query_run.py +++ b/examples/query_run.py @@ -30,8 +30,8 @@ import time from datetime import datetime -from tfe import TFEClient, TFEConfig -from tfe.models.query_run import ( +from pytfe import TFEClient, TFEConfig +from pytfe.models.query_run import ( QueryRunCancelOptions, QueryRunCreateOptions, QueryRunForceCancelOptions, diff --git a/examples/registry_module_complete_test.py b/examples/registry_module.py similarity index 99% rename from examples/registry_module_complete_test.py rename to examples/registry_module.py index a6eddd95..79ac22e0 100644 --- a/examples/registry_module_complete_test.py +++ b/examples/registry_module.py @@ -2,7 +2,7 @@ """ Complete Registry Module Testing Suite -This file contains individual tests for all 15 registry module functions implemented in src/tfe/resources/registry_module.py: +This file contains individual tests for all 15 registry module functions: PUBLIC FUNCTIONS AVAILABLE FOR TESTING: 1. list() - List registry modules in organization @@ -39,9 +39,9 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient, TFEConfig -from tfe.errors import NotFound -from tfe.models.registry_module_types import ( +from pytfe import TFEClient, TFEConfig +from pytfe.errors import NotFound +from pytfe.models.registry_module_types import ( AgentExecutionMode, RegistryModuleCreateOptions, RegistryModuleCreateVersionOptions, diff --git a/examples/registry_provider_individual.py b/examples/registry_provider.py similarity index 99% rename from examples/registry_provider_individual.py rename to examples/registry_provider.py index bf26faf2..31e5b3b9 100644 --- a/examples/registry_provider_individual.py +++ b/examples/registry_provider.py @@ -23,8 +23,8 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient -from tfe.models.registry_provider_types import ( +from pytfe import TFEClient +from pytfe.models.registry_provider_types import ( RegistryName, RegistryProviderCreateOptions, RegistryProviderID, diff --git a/examples/reserved_tag_key.py b/examples/reserved_tag_key.py index 1c0d1eaa..8eeb5e7d 100644 --- a/examples/reserved_tag_key.py +++ b/examples/reserved_tag_key.py @@ -18,9 +18,9 @@ # Add the source directory to the path for direct execution sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient, TFEConfig -from tfe.errors import TFEError -from tfe.models import ( +from pytfe import TFEClient, TFEConfig +from pytfe.errors import TFEError +from pytfe.models import ( ReservedTagKeyCreateOptions, ReservedTagKeyListOptions, ReservedTagKeyUpdateOptions, diff --git a/examples/run.py b/examples/run.py index 3dd588e0..02daab3d 100644 --- a/examples/run.py +++ b/examples/run.py @@ -4,8 +4,8 @@ import os from datetime import datetime -from tfe import TFEClient, TFEConfig -from tfe.models.run import ( +from pytfe import TFEClient, TFEConfig +from pytfe.models.run import ( RunCreateOptions, RunIncludeOpt, RunListForOrganizationOptions, @@ -141,7 +141,7 @@ def main(): workspace_data = client.workspaces.read_by_id(args.workspace_id) # Create the workspace object that run models expect - from tfe.models.workspace import Workspace + from pytfe.models.workspace import Workspace workspace = Workspace( id=workspace_data.id, diff --git a/examples/run_events.py b/examples/run_events.py index e075f8f4..df0f782c 100644 --- a/examples/run_events.py +++ b/examples/run_events.py @@ -37,8 +37,8 @@ import argparse import os -from tfe import TFEClient, TFEConfig -from tfe.models.run_event import ( +from pytfe import TFEClient, TFEConfig +from pytfe.models.run_event import ( RunEventIncludeOpt, RunEventListOptions, RunEventReadOptions, diff --git a/examples/run_task.py b/examples/run_task.py index 0418564b..9b669754 100644 --- a/examples/run_task.py +++ b/examples/run_task.py @@ -1,8 +1,8 @@ import time import traceback -from tfe import TFEClient, TFEConfig -from tfe.models.run_task import ( +from pytfe import TFEClient, TFEConfig +from pytfe.models.run_task import ( RunTaskCreateOptions, RunTaskIncludeOptions, RunTaskListOptions, diff --git a/examples/run_trigger.py b/examples/run_trigger.py index e0eec19e..97023a95 100644 --- a/examples/run_trigger.py +++ b/examples/run_trigger.py @@ -1,14 +1,14 @@ import time import traceback -from tfe import TFEClient, TFEConfig -from tfe.models.run_trigger import ( +from pytfe import TFEClient, TFEConfig +from pytfe.models.run_trigger import ( RunTriggerCreateOptions, RunTriggerFilterOp, RunTriggerIncludeOp, RunTriggerListOptions, ) -from tfe.models.workspace import Workspace +from pytfe.models.workspace import Workspace def run_trigger_list(client, workspace_id): diff --git a/examples/ssh_keys.py b/examples/ssh_keys.py index 0dc66934..7d2efa39 100644 --- a/examples/ssh_keys.py +++ b/examples/ssh_keys.py @@ -28,9 +28,9 @@ # Add the source directory to the path for direct execution sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient, TFEConfig -from tfe.errors import NotFound, TFEError -from tfe.models import SSHKeyCreateOptions, SSHKeyListOptions, SSHKeyUpdateOptions +from pytfe import TFEClient, TFEConfig +from pytfe.errors import NotFound, TFEError +from pytfe.models import SSHKeyCreateOptions, SSHKeyListOptions, SSHKeyUpdateOptions # Configuration TFE_TOKEN = os.getenv("TFE_TOKEN") diff --git a/examples/state_versions.py b/examples/state_versions.py index 581b0e81..03278bfc 100644 --- a/examples/state_versions.py +++ b/examples/state_versions.py @@ -4,14 +4,14 @@ import os from pathlib import Path -from tfe import TFEClient, TFEConfig -from tfe.errors import ErrStateVersionUploadNotSupported -from tfe.models.state_version import ( +from pytfe import TFEClient, TFEConfig +from pytfe.errors import ErrStateVersionUploadNotSupported +from pytfe.models.state_version import ( StateVersionCreateOptions, StateVersionCurrentOptions, StateVersionListOptions, ) -from tfe.models.state_version_output import StateVersionOutputsListOptions +from pytfe.models.state_version_output import StateVersionOutputsListOptions def _print_header(title: str): diff --git a/examples/variable_sets_example.py b/examples/variable_sets.py similarity index 99% rename from examples/variable_sets_example.py rename to examples/variable_sets.py index df621026..9ca22116 100644 --- a/examples/variable_sets_example.py +++ b/examples/variable_sets.py @@ -15,8 +15,8 @@ import os -from tfe import TFEClient, TFEConfig -from tfe.types import ( +from pytfe import TFEClient, TFEConfig +from pytfe.types import ( CategoryType, Parent, Project, @@ -192,7 +192,7 @@ def variable_set_example(): print("7. Workspace operations example...") try: # List some workspaces first - from tfe.types import WorkspaceListOptions + from pytfe.types import WorkspaceListOptions workspace_options = WorkspaceListOptions(page_size=5) workspaces = list( @@ -271,7 +271,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 pytfe.types import VariableSetReadOptions read_options = VariableSetReadOptions( include=[VariableSetIncludeOpt.VARS, VariableSetIncludeOpt.WORKSPACES] diff --git a/examples/variables.py b/examples/variables.py index b3987d9a..d5f4b0c2 100644 --- a/examples/variables.py +++ b/examples/variables.py @@ -11,8 +11,8 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient, TFEConfig -from tfe.types import CategoryType, VariableCreateOptions, VariableUpdateOptions +from pytfe import TFEClient, TFEConfig +from pytfe.types import CategoryType, VariableCreateOptions, VariableUpdateOptions def main(): diff --git a/examples/workspace.py b/examples/workspace.py index d5c67c59..13fddbbf 100644 --- a/examples/workspace.py +++ b/examples/workspace.py @@ -22,8 +22,8 @@ # Add the source directory to the path for direct execution sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from tfe import TFEClient, TFEConfig -from tfe.types import ( +from pytfe import TFEClient, TFEConfig +from pytfe.types import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, ExecutionMode, @@ -386,7 +386,7 @@ def demo_remote_state_consumer_operations( print("\n Getting real workspaces for consumer demonstration...") # Get existing workspaces from the organization to use as examples - from tfe.types import WorkspaceListOptions + from pytfe.types import WorkspaceListOptions org_list_options = WorkspaceListOptions(page_size=5) @@ -434,7 +434,7 @@ def demo_remote_state_consumer_operations( ) # Create mock workspaces for demonstration only - from tfe.types import Workspace + from pytfe.types import Workspace demo_consumer_1 = Workspace( id="ws-demo-consumer-1", diff --git a/pyproject.toml b/pyproject.toml index 1b11eeb7..31f289f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ requires = ["hatchling>=1.21"] build-backend = "hatchling.build" [project] -name = "tfe" -version = "0.1.0a1" +name = "pytfe" +version = "0.0.1-alpha" description = "Official Python SDK for HashiCorp Terraform Cloud / Terraform Enterprise (TFE) API v2" readme = "README.md" license = { text = "MPL-2.0" } @@ -14,7 +14,6 @@ dependencies = [ "httpx>=0.27.0,<0.29.0", "pydantic>=2.6,<3", "typing-extensions>=4.8", - "anyio>=4.0", "h2>=4.3.0" ] classifiers = [ @@ -42,7 +41,7 @@ dev = [ "Bug Tracker" = "https://github.com/hashicorp/python-tfe/issues" [tool.hatch.build.targets.wheel] -packages = ["src/tfe"] +packages = ["src/pytfe"] # Ruff configuration [tool.ruff] @@ -114,4 +113,7 @@ show_error_codes = true [[tool.mypy.overrides]] module = "tests.*" -disallow_untyped_defs = false \ No newline at end of file +disallow_untyped_defs = false + +[tool.hatch.build.targets.sdist] +include = ["src/**", "README.md", "LICENSE", "docs/**"] \ No newline at end of file diff --git a/src/tfe/__init__.py b/src/pytfe/__init__.py similarity index 100% rename from src/tfe/__init__.py rename to src/pytfe/__init__.py diff --git a/src/tfe/_http.py b/src/pytfe/_http.py similarity index 75% rename from src/tfe/_http.py rename to src/pytfe/_http.py index 9c51efd2..04743bdd 100644 --- a/src/tfe/_http.py +++ b/src/pytfe/_http.py @@ -6,7 +6,6 @@ from typing import Any from urllib.parse import urljoin -import anyio import httpx from ._jsonapi import build_headers, parse_error_payload @@ -109,45 +108,6 @@ def request( self._raise_if_error(resp) return resp - async def arequest( - self, - method: str, - path: str, - *, - params: Mapping[str, Any] | None = None, - json_body: Mapping[str, Any] | None = None, - data: bytes | None = None, - headers: dict[str, str] | None = None, - allow_redirects: bool = True, - ) -> httpx.Response: - url = f"{self.base}{path}" - hdrs = dict(self.headers) - hdrs.update(headers or {}) - attempt = 0 - while True: - try: - resp = await self._async.request( - method, - url, - params=params, - json=json_body, - content=data, - headers=hdrs, - follow_redirects=allow_redirects, - ) - except httpx.HTTPError as e: - if attempt >= self.max_retries: - raise ServerError(str(e)) from e - await self._asleep(attempt, None) - attempt += 1 - continue - if resp.status_code in _RETRY_STATUSES and attempt < self.max_retries: - retry_after = _parse_retry_after(resp) - await self._asleep(attempt, retry_after) - attempt += 1 - continue - self._raise_if_error(resp) - return resp def _sleep(self, attempt: int, retry_after: float | None) -> None: if retry_after is not None: @@ -156,12 +116,6 @@ def _sleep(self, attempt: int, retry_after: float | None) -> None: delay = min(self.backoff_cap, self.backoff_base * (2**attempt)) time.sleep(delay) - async def _asleep(self, attempt: int, retry_after: float | None) -> None: - if retry_after is not None: - await anyio.sleep(retry_after) - return - delay = min(self.backoff_cap, self.backoff_base * (2**attempt)) - await anyio.sleep(delay) def _raise_if_error(self, resp: httpx.Response) -> None: status = resp.status_code diff --git a/src/tfe/_jsonapi.py b/src/pytfe/_jsonapi.py similarity index 95% rename from src/tfe/_jsonapi.py rename to src/pytfe/_jsonapi.py index aa0e426e..3a12f604 100644 --- a/src/tfe/_jsonapi.py +++ b/src/pytfe/_jsonapi.py @@ -4,7 +4,7 @@ def build_headers(user_agent_suffix: str | None = None) -> dict[str, str]: - ua = "python-tfe/0.1" + ua = "pytfe/0.1" if user_agent_suffix: ua = f"{ua} {user_agent_suffix}" return { diff --git a/src/tfe/client.py b/src/pytfe/client.py similarity index 97% rename from src/tfe/client.py rename to src/pytfe/client.py index 90e0f80e..d0bb7033 100644 --- a/src/tfe/client.py +++ b/src/pytfe/client.py @@ -82,4 +82,7 @@ def __init__(self, config: TFEConfig | None = None): self.reserved_tag_key = ReservedTagKey(self._transport) def close(self) -> None: - pass + try: + self._transport._sync.close() + except Exception: + pass diff --git a/src/tfe/config.py b/src/pytfe/config.py similarity index 100% rename from src/tfe/config.py rename to src/pytfe/config.py diff --git a/src/tfe/errors.py b/src/pytfe/errors.py similarity index 100% rename from src/tfe/errors.py rename to src/pytfe/errors.py diff --git a/src/tfe/models/__init__.py b/src/pytfe/models/__init__.py similarity index 100% rename from src/tfe/models/__init__.py rename to src/pytfe/models/__init__.py diff --git a/src/tfe/models/agent.py b/src/pytfe/models/agent.py similarity index 100% rename from src/tfe/models/agent.py rename to src/pytfe/models/agent.py diff --git a/src/tfe/models/apply.py b/src/pytfe/models/apply.py similarity index 100% rename from src/tfe/models/apply.py rename to src/pytfe/models/apply.py diff --git a/src/tfe/models/comment.py b/src/pytfe/models/comment.py similarity index 100% rename from src/tfe/models/comment.py rename to src/pytfe/models/comment.py diff --git a/src/tfe/models/configuration_version.py b/src/pytfe/models/configuration_version.py similarity index 100% rename from src/tfe/models/configuration_version.py rename to src/pytfe/models/configuration_version.py diff --git a/src/tfe/models/configuration_version_types.py b/src/pytfe/models/configuration_version_types.py similarity index 100% rename from src/tfe/models/configuration_version_types.py rename to src/pytfe/models/configuration_version_types.py diff --git a/src/tfe/models/cost_estimate.py b/src/pytfe/models/cost_estimate.py similarity index 100% rename from src/tfe/models/cost_estimate.py rename to src/pytfe/models/cost_estimate.py diff --git a/src/tfe/models/oauth_client.py b/src/pytfe/models/oauth_client.py similarity index 100% rename from src/tfe/models/oauth_client.py rename to src/pytfe/models/oauth_client.py diff --git a/src/tfe/models/oauth_token.py b/src/pytfe/models/oauth_token.py similarity index 100% rename from src/tfe/models/oauth_token.py rename to src/pytfe/models/oauth_token.py diff --git a/src/tfe/models/organization.py b/src/pytfe/models/organization.py similarity index 100% rename from src/tfe/models/organization.py rename to src/pytfe/models/organization.py diff --git a/src/tfe/models/plan.py b/src/pytfe/models/plan.py similarity index 100% rename from src/tfe/models/plan.py rename to src/pytfe/models/plan.py diff --git a/src/tfe/models/plan_export.py b/src/pytfe/models/plan_export.py similarity index 100% rename from src/tfe/models/plan_export.py rename to src/pytfe/models/plan_export.py diff --git a/src/tfe/models/policy.py b/src/pytfe/models/policy.py similarity index 100% rename from src/tfe/models/policy.py rename to src/pytfe/models/policy.py diff --git a/src/tfe/models/policy_check.py b/src/pytfe/models/policy_check.py similarity index 100% rename from src/tfe/models/policy_check.py rename to src/pytfe/models/policy_check.py diff --git a/src/tfe/models/policy_set.py b/src/pytfe/models/policy_set.py similarity index 100% rename from src/tfe/models/policy_set.py rename to src/pytfe/models/policy_set.py diff --git a/src/tfe/models/query_run.py b/src/pytfe/models/query_run.py similarity index 100% rename from src/tfe/models/query_run.py rename to src/pytfe/models/query_run.py diff --git a/src/tfe/models/registry_module_types.py b/src/pytfe/models/registry_module_types.py similarity index 100% rename from src/tfe/models/registry_module_types.py rename to src/pytfe/models/registry_module_types.py diff --git a/src/tfe/models/registry_provider_types.py b/src/pytfe/models/registry_provider_types.py similarity index 100% rename from src/tfe/models/registry_provider_types.py rename to src/pytfe/models/registry_provider_types.py diff --git a/src/tfe/models/reserved_tag_key.py b/src/pytfe/models/reserved_tag_key.py similarity index 100% rename from src/tfe/models/reserved_tag_key.py rename to src/pytfe/models/reserved_tag_key.py diff --git a/src/tfe/models/run.py b/src/pytfe/models/run.py similarity index 100% rename from src/tfe/models/run.py rename to src/pytfe/models/run.py diff --git a/src/tfe/models/run_event.py b/src/pytfe/models/run_event.py similarity index 100% rename from src/tfe/models/run_event.py rename to src/pytfe/models/run_event.py diff --git a/src/tfe/models/run_task.py b/src/pytfe/models/run_task.py similarity index 100% rename from src/tfe/models/run_task.py rename to src/pytfe/models/run_task.py diff --git a/src/tfe/models/run_trigger.py b/src/pytfe/models/run_trigger.py similarity index 100% rename from src/tfe/models/run_trigger.py rename to src/pytfe/models/run_trigger.py diff --git a/src/tfe/models/ssh_key.py b/src/pytfe/models/ssh_key.py similarity index 100% rename from src/tfe/models/ssh_key.py rename to src/pytfe/models/ssh_key.py diff --git a/src/tfe/models/state_version.py b/src/pytfe/models/state_version.py similarity index 100% rename from src/tfe/models/state_version.py rename to src/pytfe/models/state_version.py diff --git a/src/tfe/models/state_version_output.py b/src/pytfe/models/state_version_output.py similarity index 100% rename from src/tfe/models/state_version_output.py rename to src/pytfe/models/state_version_output.py diff --git a/src/tfe/models/task_stage.py b/src/pytfe/models/task_stage.py similarity index 100% rename from src/tfe/models/task_stage.py rename to src/pytfe/models/task_stage.py diff --git a/src/tfe/models/user.py b/src/pytfe/models/user.py similarity index 100% rename from src/tfe/models/user.py rename to src/pytfe/models/user.py diff --git a/src/tfe/models/workspace.py b/src/pytfe/models/workspace.py similarity index 100% rename from src/tfe/models/workspace.py rename to src/pytfe/models/workspace.py diff --git a/src/tfe/models/workspace_run_task.py b/src/pytfe/models/workspace_run_task.py similarity index 100% rename from src/tfe/models/workspace_run_task.py rename to src/pytfe/models/workspace_run_task.py diff --git a/src/tfe/resources/_base.py b/src/pytfe/resources/_base.py similarity index 55% rename from src/tfe/resources/_base.py rename to src/pytfe/resources/_base.py index a0eaa715..95ecaed8 100644 --- a/src/tfe/resources/_base.py +++ b/src/pytfe/resources/_base.py @@ -31,30 +31,3 @@ def _list( if len(data) < page_size: break page += 1 - - -""" -Warning: Do Not Use this Async Service as its not stable with HashiCorp API. -""" - - -class _AService: - def __init__(self, t: HTTPTransport) -> None: - self.t = t - - async def _alist( - self, path: str, *, params: dict | None = None - ) -> AsyncIterator[dict[str, Any]]: - page = 1 - while True: - p = dict(params or {}) - p.setdefault("page[number]", page) - p.setdefault("page[size]", 100) - r = await self.t.arequest("GET", path, params=p) - data = r.json().get("data", []) - for item in data: - yield item - page_size = int(p["page[size]"]) - if len(data) < page_size: - break - page += 1 diff --git a/src/tfe/resources/admin/settings.py b/src/pytfe/resources/admin/settings.py similarity index 100% rename from src/tfe/resources/admin/settings.py rename to src/pytfe/resources/admin/settings.py diff --git a/src/tfe/resources/agent_pools.py b/src/pytfe/resources/agent_pools.py similarity index 100% rename from src/tfe/resources/agent_pools.py rename to src/pytfe/resources/agent_pools.py diff --git a/src/tfe/resources/agents.py b/src/pytfe/resources/agents.py similarity index 100% rename from src/tfe/resources/agents.py rename to src/pytfe/resources/agents.py diff --git a/src/tfe/resources/apply.py b/src/pytfe/resources/apply.py similarity index 100% rename from src/tfe/resources/apply.py rename to src/pytfe/resources/apply.py diff --git a/src/tfe/resources/configuration_version.py b/src/pytfe/resources/configuration_version.py similarity index 100% rename from src/tfe/resources/configuration_version.py rename to src/pytfe/resources/configuration_version.py diff --git a/src/tfe/resources/oauth_client.py b/src/pytfe/resources/oauth_client.py similarity index 100% rename from src/tfe/resources/oauth_client.py rename to src/pytfe/resources/oauth_client.py diff --git a/src/tfe/resources/oauth_token.py b/src/pytfe/resources/oauth_token.py similarity index 100% rename from src/tfe/resources/oauth_token.py rename to src/pytfe/resources/oauth_token.py diff --git a/src/tfe/resources/organizations.py b/src/pytfe/resources/organizations.py similarity index 100% rename from src/tfe/resources/organizations.py rename to src/pytfe/resources/organizations.py diff --git a/src/tfe/resources/plan.py b/src/pytfe/resources/plan.py similarity index 100% rename from src/tfe/resources/plan.py rename to src/pytfe/resources/plan.py diff --git a/src/tfe/resources/policy.py b/src/pytfe/resources/policy.py similarity index 100% rename from src/tfe/resources/policy.py rename to src/pytfe/resources/policy.py diff --git a/src/tfe/resources/projects.py b/src/pytfe/resources/projects.py similarity index 100% rename from src/tfe/resources/projects.py rename to src/pytfe/resources/projects.py diff --git a/src/tfe/resources/query_run.py b/src/pytfe/resources/query_run.py similarity index 100% rename from src/tfe/resources/query_run.py rename to src/pytfe/resources/query_run.py diff --git a/src/tfe/resources/registry_module.py b/src/pytfe/resources/registry_module.py similarity index 100% rename from src/tfe/resources/registry_module.py rename to src/pytfe/resources/registry_module.py diff --git a/src/tfe/resources/registry_provider.py b/src/pytfe/resources/registry_provider.py similarity index 100% rename from src/tfe/resources/registry_provider.py rename to src/pytfe/resources/registry_provider.py diff --git a/src/tfe/resources/reserved_tag_key.py b/src/pytfe/resources/reserved_tag_key.py similarity index 100% rename from src/tfe/resources/reserved_tag_key.py rename to src/pytfe/resources/reserved_tag_key.py diff --git a/src/tfe/resources/run.py b/src/pytfe/resources/run.py similarity index 100% rename from src/tfe/resources/run.py rename to src/pytfe/resources/run.py diff --git a/src/tfe/resources/run_event.py b/src/pytfe/resources/run_event.py similarity index 100% rename from src/tfe/resources/run_event.py rename to src/pytfe/resources/run_event.py diff --git a/src/tfe/resources/run_task.py b/src/pytfe/resources/run_task.py similarity index 100% rename from src/tfe/resources/run_task.py rename to src/pytfe/resources/run_task.py diff --git a/src/tfe/resources/run_trigger.py b/src/pytfe/resources/run_trigger.py similarity index 100% rename from src/tfe/resources/run_trigger.py rename to src/pytfe/resources/run_trigger.py diff --git a/src/tfe/resources/ssh_keys.py b/src/pytfe/resources/ssh_keys.py similarity index 100% rename from src/tfe/resources/ssh_keys.py rename to src/pytfe/resources/ssh_keys.py diff --git a/src/tfe/resources/state_version_outputs.py b/src/pytfe/resources/state_version_outputs.py similarity index 100% rename from src/tfe/resources/state_version_outputs.py rename to src/pytfe/resources/state_version_outputs.py diff --git a/src/tfe/resources/state_versions.py b/src/pytfe/resources/state_versions.py similarity index 100% rename from src/tfe/resources/state_versions.py rename to src/pytfe/resources/state_versions.py diff --git a/src/tfe/resources/variable.py b/src/pytfe/resources/variable.py similarity index 100% rename from src/tfe/resources/variable.py rename to src/pytfe/resources/variable.py diff --git a/src/tfe/resources/variable_sets.py b/src/pytfe/resources/variable_sets.py similarity index 99% rename from src/tfe/resources/variable_sets.py rename to src/pytfe/resources/variable_sets.py index bc0e0db7..9dbb2f6b 100644 --- a/src/tfe/resources/variable_sets.py +++ b/src/pytfe/resources/variable_sets.py @@ -3,9 +3,9 @@ import builtins from typing import Any -from tfe._http import HTTPTransport -from tfe.resources._base import _Service -from tfe.types import ( +from .._http import HTTPTransport +from ._base import _Service +from ..types import ( VariableSet, VariableSetApplyToProjectsOptions, VariableSetApplyToWorkspacesOptions, diff --git a/src/tfe/resources/workspaces.py b/src/pytfe/resources/workspaces.py similarity index 100% rename from src/tfe/resources/workspaces.py rename to src/pytfe/resources/workspaces.py diff --git a/src/tfe/types.py b/src/pytfe/types.py similarity index 100% rename from src/tfe/types.py rename to src/pytfe/types.py diff --git a/src/tfe/utils.py b/src/pytfe/utils.py similarity index 80% rename from src/tfe/utils.py rename to src/pytfe/utils.py index 4b48fdea..71df5028 100644 --- a/src/tfe/utils.py +++ b/src/pytfe/utils.py @@ -284,7 +284,75 @@ def validate_oauth_client_remove_projects_options( if len(options.projects) == 0: raise ValueError(ERR_PROJECT_MIN_LIMIT) +def valid_project_name(name: str) -> bool: + """Validate project name format""" + if not valid_string(name): + return False + # Project names can contain letters, numbers, spaces, hyphens, underscores, and periods + # Must be between 1 and 90 characters + if len(name) > 90: + return False + # Allow most printable characters except some special ones + # Based on Terraform Cloud API documentation + pattern = re.compile(r"^[a-zA-Z0-9\s._-]+$") + return bool(pattern.match(name)) + + +def valid_organization_name(org_name: str) -> bool: + """Validate organization name format""" + if not valid_string(org_name): + return False + # Organization names must be valid identifiers + return valid_string_id(org_name) + + +def validate_project_create_options( + organization: str, name: str, description: str | None = None +) -> None: + """Validate project creation parameters""" + if not valid_organization_name(organization): + raise ValueError("Organization name is required and must be valid") + + if not valid_string(name): + raise ValueError("Project name is required") + + if not valid_project_name(name): + raise ValueError("Project name contains invalid characters or is too long") + + if description is not None and not valid_string(description): + raise ValueError("Description must be a valid string") + + +def validate_project_update_options( + project_id: str, name: str | None = None, description: str | None = None +) -> None: + """Validate project update parameters""" + if not valid_string_id(project_id): + raise ValueError("Project ID is required") + + if name is not None: + if not valid_string(name): + raise ValueError("Project name cannot be empty") + if not valid_project_name(name): + raise ValueError("Project name contains invalid characters or is too long") + + if description is not None and not valid_string(description): + raise ValueError("Description must be a valid string") + + +def validate_project_list_options( + organization: str, query: str | None = None, name: str | None = None +) -> None: + """Validate project list options.""" + if not valid_organization_name(organization): + raise ValueError("Organization name is required and must be valid") + + if query and not valid_string(query): + raise ValueError("Query must be a valid string") + if name and not valid_project_name(name): + raise ValueError("Project name must be valid") + def pack_contents(path: str) -> io.BytesIO: """ Pack directory contents into a tar.gz archive suitable for upload. diff --git a/src/tfe/aclient.py b/src/tfe/aclient.py deleted file mode 100644 index 9580ba51..00000000 --- a/src/tfe/aclient.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Async TFE Client: This client should not be used for now. -""" - -from __future__ import annotations - -from ._http import HTTPTransport -from .config import TFEConfig - - -class AsyncTFEClient: - def __init__(self, config: TFEConfig | None = None): - cfg = config or TFEConfig.from_env() - self._transport = HTTPTransport( - cfg.address, - cfg.token, - timeout=cfg.timeout, - verify_tls=cfg.verify_tls, - user_agent_suffix=cfg.user_agent_suffix, - max_retries=cfg.max_retries, - backoff_base=cfg.backoff_base, - backoff_cap=cfg.backoff_cap, - backoff_jitter=cfg.backoff_jitter, - http2=cfg.http2, - proxies=cfg.proxies, - ca_bundle=cfg.ca_bundle, - ) diff --git a/src/tfe/project.py b/src/tfe/project.py deleted file mode 100644 index 9fcfa719..00000000 --- a/src/tfe/project.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Project-specific utility functions and validation.""" - -import re -from typing import Any - -from .utils import valid_string, valid_string_id - - -def valid_project_name(name: str) -> bool: - """Validate project name format""" - if not valid_string(name): - return False - # Project names can contain letters, numbers, spaces, hyphens, underscores, and periods - # Must be between 1 and 90 characters - if len(name) > 90: - return False - # Allow most printable characters except some special ones - # Based on Terraform Cloud API documentation - pattern = re.compile(r"^[a-zA-Z0-9\s._-]+$") - return bool(pattern.match(name)) - - -def valid_organization_name(org_name: str) -> bool: - """Validate organization name format""" - if not valid_string(org_name): - return False - # Organization names must be valid identifiers - return valid_string_id(org_name) - - -def validate_project_create_options( - organization: str, name: str, description: str | None = None -) -> None: - """Validate project creation parameters""" - if not valid_organization_name(organization): - raise ValueError("Organization name is required and must be valid") - - if not valid_string(name): - raise ValueError("Project name is required") - - if not valid_project_name(name): - raise ValueError("Project name contains invalid characters or is too long") - - if description is not None and not valid_string(description): - raise ValueError("Description must be a valid string") - - -def validate_project_update_options( - project_id: str, name: str | None = None, description: str | None = None -) -> None: - """Validate project update parameters""" - if not valid_string_id(project_id): - raise ValueError("Project ID is required") - - if name is not None: - if not valid_string(name): - raise ValueError("Project name cannot be empty") - if not valid_project_name(name): - raise ValueError("Project name contains invalid characters or is too long") - - if description is not None and not valid_string(description): - raise ValueError("Description must be a valid string") - - -def validate_project_list_options( - organization: str, query: str | None = None, name: str | None = None -) -> None: - """Validate project list options.""" - if not valid_organization_name(organization): - raise ValueError("Organization name is required and must be valid") - - if query and not valid_string(query): - raise ValueError("Query must be a valid string") - - if name and not valid_project_name(name): - raise ValueError("Project name must be valid") - - -def _safe_str(value: Any, default: str = "") -> str: - """Safely convert a value to string with optional default.""" - if value is None: - return default - return str(value) From 9cc5653dbcffda4e343c218178a9d8756eea5fd6 Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Thu, 9 Oct 2025 13:45:16 +0530 Subject: [PATCH 2/9] update readme and other changes --- README.md | 2 +- examples/agent.py | 2 +- examples/agent_pool.py | 2 +- examples/oauth_client.py | 2 +- examples/oauth_token.py | 2 +- examples/policy.py | 2 +- examples/query_run.py | 2 +- examples/registry_module.py | 2 +- examples/registry_provider.py | 2 +- src/pytfe/__init__.py | 4 ++-- src/pytfe/_http.py | 5 +---- src/pytfe/py.typed | 0 tests/units/test_agent_pools.py | 10 +++++----- tests/units/test_agents.py | 8 ++++---- tests/units/test_apply.py | 6 +++--- tests/units/test_configuration_version.py | 10 +++++----- tests/units/test_oauth_client.py | 8 ++++---- tests/units/test_oauth_token.py | 8 ++++---- tests/units/test_plan.py | 4 ++-- tests/units/test_policy.py | 10 +++++----- tests/units/test_project.py | 4 ++-- tests/units/test_query_run.py | 6 +++--- tests/units/test_reserved_tag_key.py | 8 ++++---- tests/units/test_run.py | 10 +++++----- tests/units/test_run_task.py | 10 +++++----- tests/units/test_run_trigger.py | 10 +++++----- tests/units/test_ssh_keys.py | 8 ++++---- tests/units/test_transport.py | 4 ++-- tests/units/test_variable_sets.py | 4 ++-- tests/units/test_workspaces.py | 8 ++++---- 30 files changed, 80 insertions(+), 83 deletions(-) create mode 100644 src/pytfe/py.typed diff --git a/README.md b/README.md index 67d97008..1c0d9e7f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![PyPI](https://img.shields.io/pypi/v/pytfe.svg)](https://pypi.org/project/pytfe/) [![Python Versions](https://img.shields.io/pypi/pyversions/pytfe.svg)](https://pypi.org/project/pytfe/) -[![CI](https://github.com/hashicorp/python-tfe/actions/workflows/ci.yml/badge.svg)](https://github.com/hashicorp/python-tfe/actions/workflows/ci.yml) +[![Test](https://github.com/hashicorp/python-tfe/actions/workflows/test.yml/badge.svg)](https://github.com/hashicorp/python-tfe/actions/workflows/test.yml) [![License](https://img.shields.io/github/license/hashicorp/python-tfe.svg)](./LICENSE) [![Issues](https://img.shields.io/github/issues/hashicorp/python-tfe.svg)](https://github.com/hashicorp/python-tfe/issues) diff --git a/examples/agent.py b/examples/agent.py index 1ba0a0a9..d756abfa 100644 --- a/examples/agent.py +++ b/examples/agent.py @@ -25,7 +25,7 @@ from pytfe.client import TFEClient from pytfe.config import TFEConfig from pytfe.errors import NotFound -from pytfe.models.agent import AgentListOptions +from pytfe.models import AgentListOptions def main(): diff --git a/examples/agent_pool.py b/examples/agent_pool.py index e347736a..71e920f2 100644 --- a/examples/agent_pool.py +++ b/examples/agent_pool.py @@ -22,7 +22,7 @@ from pytfe import TFEClient, TFEConfig from pytfe.errors import NotFound -from pytfe.models.agent import ( +from pytfe.models import ( AgentPoolAllowedWorkspacePolicy, AgentPoolCreateOptions, AgentPoolListOptions, diff --git a/examples/oauth_client.py b/examples/oauth_client.py index f449ea23..96671dd0 100644 --- a/examples/oauth_client.py +++ b/examples/oauth_client.py @@ -37,7 +37,7 @@ from pytfe import TFEClient, TFEConfig from pytfe.errors import NotFound -from pytfe.models.oauth_client import ( +from pytfe.models import ( OAuthClientAddProjectsOptions, OAuthClientCreateOptions, OAuthClientIncludeOpt, diff --git a/examples/oauth_token.py b/examples/oauth_token.py index 70c6e798..725fb592 100644 --- a/examples/oauth_token.py +++ b/examples/oauth_token.py @@ -30,7 +30,7 @@ from pytfe import TFEClient, TFEConfig from pytfe.errors import NotFound -from pytfe.models.oauth_token import OAuthTokenListOptions, OAuthTokenUpdateOptions +from pytfe.models import OAuthTokenListOptions, OAuthTokenUpdateOptions def main(): diff --git a/examples/policy.py b/examples/policy.py index 46314a39..1b849be8 100644 --- a/examples/policy.py +++ b/examples/policy.py @@ -23,7 +23,7 @@ from pathlib import Path from pytfe import TFEClient, TFEConfig -from pytfe.models.policy import ( +from pytfe.models import ( EnforcementLevel, PolicyCreateOptions, PolicyListOptions, diff --git a/examples/query_run.py b/examples/query_run.py index f3ec6829..610caa77 100644 --- a/examples/query_run.py +++ b/examples/query_run.py @@ -31,7 +31,7 @@ from datetime import datetime from pytfe import TFEClient, TFEConfig -from pytfe.models.query_run import ( +from pytfe.models import ( QueryRunCancelOptions, QueryRunCreateOptions, QueryRunForceCancelOptions, diff --git a/examples/registry_module.py b/examples/registry_module.py index 79ac22e0..0be3b990 100644 --- a/examples/registry_module.py +++ b/examples/registry_module.py @@ -41,7 +41,7 @@ from pytfe import TFEClient, TFEConfig from pytfe.errors import NotFound -from pytfe.models.registry_module_types import ( +from pytfe.models import ( AgentExecutionMode, RegistryModuleCreateOptions, RegistryModuleCreateVersionOptions, diff --git a/examples/registry_provider.py b/examples/registry_provider.py index 31e5b3b9..bcd3e328 100644 --- a/examples/registry_provider.py +++ b/examples/registry_provider.py @@ -24,7 +24,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) from pytfe import TFEClient -from pytfe.models.registry_provider_types import ( +from pytfe.models import ( RegistryName, RegistryProviderCreateOptions, RegistryProviderID, diff --git a/src/pytfe/__init__.py b/src/pytfe/__init__.py index 8c1b6c66..bc7be167 100644 --- a/src/pytfe/__init__.py +++ b/src/pytfe/__init__.py @@ -1,5 +1,5 @@ -from . import errors +from . import errors, models from .client import TFEClient from .config import TFEConfig -__all__ = ["TFEConfig", "TFEClient", "errors"] +__all__ = ["TFEConfig", "TFEClient", "errors", "models"] diff --git a/src/pytfe/_http.py b/src/pytfe/_http.py index 04743bdd..1c82d41b 100644 --- a/src/pytfe/_http.py +++ b/src/pytfe/_http.py @@ -53,10 +53,7 @@ def __init__( self.proxies = proxies self.ca_bundle = ca_bundle self._sync = httpx.Client( - http2=http2, timeout=timeout, verify=ca_bundle or verify_tls - ) # proxies=proxies - self._async = httpx.AsyncClient( - http2=http2, timeout=timeout, verify=ca_bundle or verify_tls + http2=http2, timeout=timeout, verify=ca_bundle or verify_tls, proxies=proxies ) # proxies=proxies def _build_url(self, path: str) -> str: diff --git a/src/pytfe/py.typed b/src/pytfe/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/tests/units/test_agent_pools.py b/tests/units/test_agent_pools.py index f4e29ee3..a113ac6a 100644 --- a/tests/units/test_agent_pools.py +++ b/tests/units/test_agent_pools.py @@ -15,8 +15,8 @@ import pytest -from tfe.errors import AuthError, NotFound, ValidationError -from tfe.models.agent import ( +from pytfe.errors import AuthError, NotFound, ValidationError +from pytfe.models.agent import ( AgentPool, AgentPoolAllowedWorkspacePolicy, AgentPoolCreateOptions, @@ -98,7 +98,7 @@ def mock_transport(self): @pytest.fixture def agent_pools_service(self, mock_transport): """Create agent pools service with mocked transport.""" - from tfe.resources.agent_pools import AgentPools + from pytfe.resources.agent_pools import AgentPools return AgentPools(mock_transport) @@ -276,7 +276,7 @@ def mock_transport(self): @pytest.fixture def agent_tokens_service(self, mock_transport): """Create agent tokens service with mocked transport.""" - from tfe.resources.agents import AgentTokens + from pytfe.resources.agents import AgentTokens return AgentTokens(mock_transport) @@ -396,7 +396,7 @@ def mock_transport(self): @pytest.fixture def agent_pools_service(self, mock_transport): """Create agent pools service with mocked transport.""" - from tfe.resources.agent_pools import AgentPools + from pytfe.resources.agent_pools import AgentPools return AgentPools(mock_transport) diff --git a/tests/units/test_agents.py b/tests/units/test_agents.py index 446c98c4..94e9b333 100644 --- a/tests/units/test_agents.py +++ b/tests/units/test_agents.py @@ -14,8 +14,8 @@ import pytest -from tfe.errors import AuthError, NotFound -from tfe.models.agent import ( +from pytfe.errors import AuthError, NotFound +from pytfe.models.agent import ( Agent, AgentStatus, ) @@ -72,7 +72,7 @@ def mock_transport(self): @pytest.fixture def agents_service(self, mock_transport): """Create agents service with mocked transport.""" - from tfe.resources.agents import Agents + from pytfe.resources.agents import Agents return Agents(mock_transport) @@ -154,7 +154,7 @@ def mock_transport(self): @pytest.fixture def agents_service(self, mock_transport): """Create agents service with mocked transport.""" - from tfe.resources.agents import Agents + from pytfe.resources.agents import Agents return Agents(mock_transport) diff --git a/tests/units/test_apply.py b/tests/units/test_apply.py index 7553873d..92da1903 100644 --- a/tests/units/test_apply.py +++ b/tests/units/test_apply.py @@ -5,9 +5,9 @@ import unittest from unittest.mock import MagicMock, patch -from tfe.errors import InvalidApplyIDError -from tfe.models.apply import Apply -from tfe.resources.apply import Applies +from pytfe.errors import InvalidApplyIDError +from pytfe.models.apply import Apply +from pytfe.resources.apply import Applies class TestApplies(unittest.TestCase): diff --git a/tests/units/test_configuration_version.py b/tests/units/test_configuration_version.py index 27753518..240fc162 100644 --- a/tests/units/test_configuration_version.py +++ b/tests/units/test_configuration_version.py @@ -21,8 +21,8 @@ import pytest -from src.tfe.errors import NotFound, TFEError -from src.tfe.models.configuration_version_types import ( +from src.pytfe.errors import NotFound, TFEError +from src.pytfe.models.configuration_version_types import ( ConfigurationSource, ConfigurationStatus, ConfigurationVersionCreateOptions, @@ -30,7 +30,7 @@ ConfigurationVersionReadOptions, ConfigVerIncludeOpt, ) -from src.tfe.resources.configuration_version import ConfigurationVersions +from src.pytfe.resources.configuration_version import ConfigurationVersions @pytest.fixture @@ -645,7 +645,7 @@ class TestConfigurationVersionsValidation: def test_valid_string_id_valid(self, configuration_versions_service): """Test valid_string_id with valid configuration version ID.""" - from src.tfe.utils import valid_string_id + from src.pytfe.utils import valid_string_id # This should return True and not raise an exception result = valid_string_id("cv-ntv3HbhJqvFzamy7") @@ -653,7 +653,7 @@ def test_valid_string_id_valid(self, configuration_versions_service): def test_valid_string_id_invalid(self, configuration_versions_service): """Test valid_string_id with invalid configuration version ID.""" - from src.tfe.utils import valid_string_id + from src.pytfe.utils import valid_string_id # This should return False result = valid_string_id("") diff --git a/tests/units/test_oauth_client.py b/tests/units/test_oauth_client.py index 14820ee5..00e4ff33 100644 --- a/tests/units/test_oauth_client.py +++ b/tests/units/test_oauth_client.py @@ -9,12 +9,12 @@ import pytest -from src.tfe._http import HTTPTransport -from src.tfe.errors import ( +from src.pytfe._http import HTTPTransport +from src.pytfe.errors import ( ERR_INVALID_OAUTH_CLIENT_ID, ERR_INVALID_ORG, ) -from src.tfe.models.oauth_client import ( +from src.pytfe.models.oauth_client import ( OAuthClientAddProjectsOptions, OAuthClientCreateOptions, OAuthClientIncludeOpt, @@ -24,7 +24,7 @@ OAuthClientUpdateOptions, ServiceProviderType, ) -from src.tfe.resources.oauth_client import OAuthClients +from src.pytfe.resources.oauth_client import OAuthClients class TestOAuthClientParsing: diff --git a/tests/units/test_oauth_token.py b/tests/units/test_oauth_token.py index 135548fc..1af07084 100644 --- a/tests/units/test_oauth_token.py +++ b/tests/units/test_oauth_token.py @@ -9,16 +9,16 @@ import pytest -from src.tfe._http import HTTPTransport -from src.tfe.errors import ( +from src.pytfe._http import HTTPTransport +from src.pytfe.errors import ( ERR_INVALID_OAUTH_TOKEN_ID, ERR_INVALID_ORG, ) -from src.tfe.models.oauth_token import ( +from src.pytfe.models.oauth_token import ( OAuthTokenListOptions, OAuthTokenUpdateOptions, ) -from src.tfe.resources.oauth_token import OAuthTokens +from src.pytfe.resources.oauth_token import OAuthTokens class TestOAuthTokenParsing: diff --git a/tests/units/test_plan.py b/tests/units/test_plan.py index 0d933cfd..a8d5146d 100644 --- a/tests/units/test_plan.py +++ b/tests/units/test_plan.py @@ -4,8 +4,8 @@ import pytest -from tfe.errors import InvalidPlanIDError -from tfe.resources.plan import Plans +from pytfe.errors import InvalidPlanIDError +from pytfe.resources.plan import Plans class TestPlans: diff --git a/tests/units/test_policy.py b/tests/units/test_policy.py index 258cc43c..af4790c8 100644 --- a/tests/units/test_policy.py +++ b/tests/units/test_policy.py @@ -4,21 +4,21 @@ import pytest -from tfe._http import HTTPTransport -from tfe.errors import ( +from pytfe._http import HTTPTransport +from pytfe.errors import ( InvalidOrgError, InvalidPolicyIDError, RequiredNameError, ) -from tfe.models.policy import ( +from pytfe.models.policy import ( EnforcementLevel, Policy, PolicyCreateOptions, PolicyList, PolicyUpdateOptions, ) -from tfe.models.policy_set import PolicyKind -from tfe.resources.policy import Policies +from pytfe.models.policy_set import PolicyKind +from pytfe.resources.policy import Policies class TestPolicies: diff --git a/tests/units/test_project.py b/tests/units/test_project.py index 7876f748..d04e1e76 100644 --- a/tests/units/test_project.py +++ b/tests/units/test_project.py @@ -1,7 +1,7 @@ from unittest.mock import Mock -from tfe.resources.projects import Projects, _safe_str -from tfe.types import ( +from pytfe.resources.projects import Projects, _safe_str +from pytfe.types import ( EffectiveTagBinding, Project, ProjectAddTagBindingsOptions, diff --git a/tests/units/test_query_run.py b/tests/units/test_query_run.py index d9396cad..ae221152 100644 --- a/tests/units/test_query_run.py +++ b/tests/units/test_query_run.py @@ -3,9 +3,9 @@ import pytest -from tfe import TFEClient, TFEConfig -from tfe.errors import InvalidOrgError, InvalidQueryRunIDError -from tfe.models.query_run import ( +from pytfe import TFEClient, TFEConfig +from pytfe.errors import InvalidOrgError, InvalidQueryRunIDError +from pytfe.models.query_run import ( QueryRun, QueryRunCancelOptions, QueryRunCreateOptions, diff --git a/tests/units/test_reserved_tag_key.py b/tests/units/test_reserved_tag_key.py index 318d11a8..12cbc50d 100644 --- a/tests/units/test_reserved_tag_key.py +++ b/tests/units/test_reserved_tag_key.py @@ -4,17 +4,17 @@ import pytest -from src.tfe._http import HTTPTransport -from src.tfe.errors import ( +from src.pytfe._http import HTTPTransport +from src.pytfe.errors import ( InvalidOrgError, ValidationError, ) -from src.tfe.models.reserved_tag_key import ( +from src.pytfe.models.reserved_tag_key import ( ReservedTagKeyCreateOptions, ReservedTagKeyListOptions, ReservedTagKeyUpdateOptions, ) -from src.tfe.resources.reserved_tag_key import ReservedTagKey +from src.pytfe.resources.reserved_tag_key import ReservedTagKey class TestReservedTagKeyParsing: diff --git a/tests/units/test_run.py b/tests/units/test_run.py index b21c7371..bce2d2a8 100644 --- a/tests/units/test_run.py +++ b/tests/units/test_run.py @@ -4,13 +4,13 @@ import pytest -from tfe._http import HTTPTransport -from tfe.errors import ( +from pytfe._http import HTTPTransport +from pytfe.errors import ( InvalidRunIDError, RequiredWorkspaceError, TerraformVersionValidForPlanOnlyError, ) -from tfe.models.run import ( +from pytfe.models.run import ( OrganizationRunList, Run, RunApplyOptions, @@ -27,8 +27,8 @@ RunStatus, RunVariable, ) -from tfe.models.workspace import Workspace -from tfe.resources.run import Runs +from pytfe.models.workspace import Workspace +from pytfe.resources.run import Runs class TestRuns: diff --git a/tests/units/test_run_task.py b/tests/units/test_run_task.py index 8a8ce15b..a428d798 100644 --- a/tests/units/test_run_task.py +++ b/tests/units/test_run_task.py @@ -4,16 +4,16 @@ import pytest -from tfe._http import HTTPTransport -from tfe.errors import ( +from pytfe._http import HTTPTransport +from pytfe.errors import ( InvalidOrgError, InvalidRunTaskCategoryError, InvalidRunTaskIDError, InvalidRunTaskURLError, RequiredNameError, ) -from tfe.models.agent import AgentPool -from tfe.models.run_task import ( +from pytfe.models.agent import AgentPool +from pytfe.models.run_task import ( GlobalRunTaskOptions, RunTaskCreateOptions, RunTaskIncludeOptions, @@ -23,7 +23,7 @@ Stage, TaskEnforcementLevel, ) -from tfe.resources.run_task import RunTasks, _run_task_from +from pytfe.resources.run_task import RunTasks, _run_task_from class TestRunTaskFrom: diff --git a/tests/units/test_run_trigger.py b/tests/units/test_run_trigger.py index a0cbfda5..75f96ec0 100644 --- a/tests/units/test_run_trigger.py +++ b/tests/units/test_run_trigger.py @@ -5,8 +5,8 @@ import pytest -from tfe._http import HTTPTransport -from tfe.errors import ( +from pytfe._http import HTTPTransport +from pytfe.errors import ( InvalidRunTriggerIDError, InvalidRunTriggerTypeError, InvalidWorkspaceIDError, @@ -14,7 +14,7 @@ RequiredSourceableError, UnsupportedRunTriggerTypeError, ) -from tfe.models.run_trigger import ( +from pytfe.models.run_trigger import ( RunTrigger, RunTriggerCreateOptions, RunTriggerFilterOp, @@ -22,8 +22,8 @@ RunTriggerListOptions, SourceableChoice, ) -from tfe.models.workspace import Workspace -from tfe.resources.run_trigger import RunTriggers, _run_trigger_from +from pytfe.models.workspace import Workspace +from pytfe.resources.run_trigger import RunTriggers, _run_trigger_from class TestRunTriggerFrom: diff --git a/tests/units/test_ssh_keys.py b/tests/units/test_ssh_keys.py index dde405cb..057b9ac8 100644 --- a/tests/units/test_ssh_keys.py +++ b/tests/units/test_ssh_keys.py @@ -4,16 +4,16 @@ import pytest -from src.tfe._http import HTTPTransport -from src.tfe.errors import ( +from src.pytfe._http import HTTPTransport +from src.pytfe.errors import ( InvalidOrgError, InvalidSSHKeyIDError, ) -from src.tfe.models.ssh_key import ( +from src.pytfe.models.ssh_key import ( SSHKeyCreateOptions, SSHKeyUpdateOptions, ) -from src.tfe.resources.ssh_keys import SSHKeys +from src.pytfe.resources.ssh_keys import SSHKeys class TestSSHKeyParsing: diff --git a/tests/units/test_transport.py b/tests/units/test_transport.py index cad04eb0..3d4fac31 100644 --- a/tests/units/test_transport.py +++ b/tests/units/test_transport.py @@ -1,5 +1,5 @@ -from tfe._http import HTTPTransport -from tfe.config import TFEConfig +from pytfe._http import HTTPTransport +from pytfe.config import TFEConfig def test_http_transport_init(): diff --git a/tests/units/test_variable_sets.py b/tests/units/test_variable_sets.py index 77a672d5..514757f7 100644 --- a/tests/units/test_variable_sets.py +++ b/tests/units/test_variable_sets.py @@ -4,8 +4,8 @@ import pytest -from tfe.resources.variable_sets import VariableSets, VariableSetVariables -from tfe.types import ( +from pytfe.resources.variable_sets import VariableSets, VariableSetVariables +from pytfe.types import ( CategoryType, Parent, Project, diff --git a/tests/units/test_workspaces.py b/tests/units/test_workspaces.py index 29c838ca..a9bcf07f 100644 --- a/tests/units/test_workspaces.py +++ b/tests/units/test_workspaces.py @@ -9,7 +9,7 @@ import pytest -from src.tfe.errors import ( +from src.pytfe.errors import ( InvalidOrgError, InvalidSSHKeyIDError, InvalidWorkspaceIDError, @@ -19,8 +19,8 @@ RequiredSSHKeyIDError, WorkspaceMinimumLimitError, ) -from src.tfe.resources.workspaces import Workspaces, _ws_from -from src.tfe.types import ( +from src.pytfe.resources.workspaces import Workspaces, _ws_from +from src.pytfe.types import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, DataRetentionPolicySetOptions, @@ -233,7 +233,7 @@ def test_read_workspace_with_options( sample_workspace_response ) - from src.tfe.types import WorkspaceIncludeOpt + from src.pytfe.types import WorkspaceIncludeOpt options = WorkspaceReadOptions( include=[WorkspaceIncludeOpt.CURRENT_RUN, WorkspaceIncludeOpt.OUTPUTS] From afa6fce591178636e544da56383db13efa3cd72b Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Thu, 9 Oct 2025 15:56:06 +0530 Subject: [PATCH 3/9] Split types to models folder --- src/pytfe/models/common.py | 34 ++ src/pytfe/models/organization.py | 5 - src/pytfe/models/project.py | 60 ++ src/pytfe/models/run_task.py | 2 +- src/pytfe/models/run_trigger.py | 2 +- src/pytfe/models/variable.py | 45 ++ src/pytfe/models/variable_set.py | 163 ++++++ src/pytfe/models/workspace.py | 337 ++++++++++- src/pytfe/resources/organizations.py | 5 +- src/pytfe/resources/projects.py | 9 +- src/pytfe/resources/variable.py | 2 +- src/pytfe/resources/variable_sets.py | 2 +- src/pytfe/resources/workspaces.py | 18 +- src/pytfe/types.py | 810 --------------------------- 14 files changed, 666 insertions(+), 828 deletions(-) create mode 100644 src/pytfe/models/common.py create mode 100644 src/pytfe/models/project.py create mode 100644 src/pytfe/models/variable.py create mode 100644 src/pytfe/models/variable_set.py delete mode 100644 src/pytfe/types.py diff --git a/src/pytfe/models/common.py b/src/pytfe/models/common.py new file mode 100644 index 00000000..80dfcef0 --- /dev/null +++ b/src/pytfe/models/common.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from datetime import datetime +from enum import Enum +from typing import Any + +from pydantic import BaseModel, Field + + +class Pagination(BaseModel): + current_page: int + total_count: int + previous_page: int | None = None + next_page: int | None = None + total_pages: int | None = None + # Add other pagination fields as needed + + +class Tag(BaseModel): + id: str | None = None + name: str = "" + + +class TagBinding(BaseModel): + id: str | None = None + key: str + value: str | None = None + + +class EffectiveTagBinding(BaseModel): + id: str + key: str + value: str | None = None + links: dict[str, Any] = Field(default_factory=dict) \ No newline at end of file diff --git a/src/pytfe/models/organization.py b/src/pytfe/models/organization.py index 8450ddfd..8c810cd6 100644 --- a/src/pytfe/models/organization.py +++ b/src/pytfe/models/organization.py @@ -101,11 +101,6 @@ class Organization(BaseModel): data_retention_policy_choice: dict | None = None -class Project(BaseModel): - id: str - name: str - organization: str - class Capacity(BaseModel): organization: str diff --git a/src/pytfe/models/project.py b/src/pytfe/models/project.py new file mode 100644 index 00000000..de1627d5 --- /dev/null +++ b/src/pytfe/models/project.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +from datetime import datetime +from enum import Enum +from typing import Any + +from pydantic import BaseModel, Field + +from .common import TagBinding + + +class Project(BaseModel): + 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) + + diff --git a/src/pytfe/models/run_task.py b/src/pytfe/models/run_task.py index 2fde05ab..87411626 100644 --- a/src/pytfe/models/run_task.py +++ b/src/pytfe/models/run_task.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, Field -from ..types import Pagination +from ..models.common import Pagination from .agent import AgentPool from .organization import Organization from .workspace_run_task import WorkspaceRunTask diff --git a/src/pytfe/models/run_trigger.py b/src/pytfe/models/run_trigger.py index bc3d55a0..b9a4e740 100644 --- a/src/pytfe/models/run_trigger.py +++ b/src/pytfe/models/run_trigger.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, Field -from ..types import Pagination +from ..models.common import Pagination from .workspace import Workspace diff --git a/src/pytfe/models/variable.py b/src/pytfe/models/variable.py new file mode 100644 index 00000000..0953721f --- /dev/null +++ b/src/pytfe/models/variable.py @@ -0,0 +1,45 @@ + +from __future__ import annotations + +from enum import Enum + +from pydantic import BaseModel, Field + + +class CategoryType(str, Enum): + ENV = "env" + POLICY_SET = "policy-set" + TERRAFORM = "terraform" + + +class Variable(BaseModel): + id: str | None = None + key: str | None = None + value: str | None = None + description: str | None = None + category: CategoryType | None = None + hcl: bool | None = None + sensitive: bool | None = None + version_id: str | None = None + workspace: dict | None = None + +class VariableListOptions(BaseModel): + # Base pagination options would be handled by the service layer + pass + +class VariableCreateOptions(BaseModel): + key: str | None = None + value: str | None = None + description: str | None = None + category: CategoryType | None = None + hcl: bool | None = None + sensitive: bool | None = None + + +class VariableUpdateOptions(BaseModel): + key: str | None = None + value: str | None = None + description: str | None = None + category: CategoryType | None = None + hcl: bool | None = None + sensitive: bool | None = None diff --git a/src/pytfe/models/variable_set.py b/src/pytfe/models/variable_set.py new file mode 100644 index 00000000..3a761cde --- /dev/null +++ b/src/pytfe/models/variable_set.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +from datetime import datetime +from enum import Enum + +from pydantic import BaseModel, Field + +from .organization import Organization +from .workspace import Workspace +from .variable import CategoryType + +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 \ No newline at end of file diff --git a/src/pytfe/models/workspace.py b/src/pytfe/models/workspace.py index 98b5ae7e..5b316498 100644 --- a/src/pytfe/models/workspace.py +++ b/src/pytfe/models/workspace.py @@ -1,14 +1,347 @@ from __future__ import annotations +from datetime import datetime +from enum import Enum +from typing import Any + from pydantic import BaseModel, Field from .organization import ExecutionMode +from .project import Project +from .common import Pagination, Tag, TagBinding, EffectiveTagBinding +from .organization import DataRetentionPolicy, DataRetentionPolicyChoice + class Workspace(BaseModel): id: str - name: str - organization: str + name: str | None = None + organization: str | None = None execution_mode: ExecutionMode | None = None project_id: str | None = None + + # Core attributes + actions: WorkspaceActions | None = None + allow_destroy_plan: bool = False + assessments_enabled: bool = False + auto_apply: bool = False + auto_apply_run_trigger: bool = False + auto_destroy_at: datetime | None = None + auto_destroy_activity_duration: str | None = None + can_queue_destroy_plan: bool = False + created_at: datetime | None = None + description: str = "" + environment: str = "" + file_triggers_enabled: bool = False + global_remote_state: bool = False + inherits_project_auto_destroy: bool = False + locked: bool = False + migration_environment: str = "" + no_code_upgrade_available: bool = False + operations: bool = False + permissions: WorkspacePermissions | None = None + queue_all_runs: bool = False + speculative_enabled: bool = False + source: WorkspaceSource | None = None + source_name: str = "" + source_url: str = "" + structured_run_output_enabled: bool = False + terraform_version: str = "" + trigger_prefixes: list[str] = Field(default_factory=list) + trigger_patterns: list[str] = Field(default_factory=list) + vcs_repo: VCSRepo | None = None + working_directory: str = "" + updated_at: datetime | None = None + resource_count: int = 0 + apply_duration_average: float | None = None # in seconds + plan_duration_average: float | None = None # in seconds + policy_check_failures: int = 0 + run_failures: int = 0 + runs_count: int = 0 + tag_names: list[str] = Field(default_factory=list) + setting_overwrites: WorkspaceSettingOverwrites | None = None + + # Relations + 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 + ssh_key: Any | None = None # SSHKey object + outputs: list[WorkspaceOutputs] = Field(default_factory=list) tags: list[str] = Field(default_factory=list) + #tags: list[Tag] = Field(default_factory=list) + current_configuration_version: Any | None = None # ConfigurationVersion object + locked_by: LockedByChoice | None = None + variables: list[Any] = Field(default_factory=list) # Variable objects + tag_bindings: list[TagBinding] = Field(default_factory=list) + effective_tag_bindings: list[EffectiveTagBinding] = Field(default_factory=list) + + # Links + links: dict[str, Any] = Field(default_factory=dict) + data_retention_policy: DataRetentionPolicy | None = None + data_retention_policy_choice: DataRetentionPolicyChoice | None = None + + +class WorkspaceIncludeOpt(str, Enum): + ORGANIZATION = "organization" + CURRENT_CONFIG_VER = "current_configuration_version" + CURRENT_CONFIG_VER_INGRESS = "current_configuration_version.ingress_attributes" + CURRENT_RUN = "current_run" + CURRENT_RUN_PLAN = "current_run.plan" + CURRENT_RUN_CONFIG_VER = "current_run.configuration_version" + CURRENT_RUN_CONFIG_VER_INGRESS = ( + "current_run.configuration_version.ingress_attributes" + ) + EFFECTIVE_TAG_BINDINGS = "effective_tag_bindings" + LOCKED_BY = "locked_by" + README = "readme" + OUTPUTS = "outputs" + CURRENT_STATE_VER = "current-state-version" + PROJECT = "project" + +class WorkspaceSource(str, Enum): + API = "tfe-api" + MODULE = "tfe-module" + UI = "tfe-ui" + TERRAFORM = "terraform" + +class WorkspaceActions(BaseModel): + is_destroyable: bool = False + + +class WorkspacePermissions(BaseModel): + can_destroy: bool = False + can_force_unlock: bool = False + can_lock: bool = False + can_manage_run_tasks: bool = False + can_queue_apply: bool = False + can_queue_destroy: bool = False + can_queue_run: bool = False + can_read_settings: bool = False + can_unlock: bool = False + can_update: bool = False + can_update_variable: bool = False + can_force_delete: bool | None = None + + +class WorkspaceSettingOverwrites(BaseModel): + execution_mode: bool | None = None + agent_pool: bool | None = None + + +class WorkspaceOutputs(BaseModel): + id: str + name: str + sensitive: bool = False + output_type: str + value: Any | None = None + + + + +class LockedByChoice(BaseModel): + run: Any | None = None + user: Any | None = None + team: Any | None = None + + +class WorkspaceListOptions(BaseModel): + """Options for listing workspaces.""" + + # Pagination options (from ListOptions) + page_number: int | None = None + page_size: int | None = None + + # Search and filter options + search: str | None = None # search[name] - partial workspace name + tags: str | None = None # search[tags] - comma-separated tag names + exclude_tags: str | None = ( + None # search[exclude-tags] - comma-separated tag names to exclude + ) + wildcard_name: str | None = None # search[wildcard-name] - substring matching + project_id: str | None = None # filter[project][id] - project ID filter + current_run_status: str | None = ( + None # filter[current-run][status] - run status filter + ) + + # Tag binding filters (not URL encoded, handled specially) + tag_bindings: list[TagBinding] = Field(default_factory=list) + + # Include related resources + include: list[WorkspaceIncludeOpt] = Field(default_factory=list) + + # Sorting options + sort: str | None = ( + None # "name" (default) or "current-run.created-at", prepend "-" to reverse + ) + + +class WorkspaceReadOptions(BaseModel): + include: list[WorkspaceIncludeOpt] = Field(default_factory=list) + + +class WorkspaceCreateOptions(BaseModel): + name: str + type: str = "workspaces" + agent_pool_id: str | None = None + allow_destroy_plan: bool | None = None + assessments_enabled: bool | None = None + auto_apply: bool | None = None + auto_apply_run_trigger: bool | None = None + auto_destroy_at: datetime | None = None + auto_destroy_activity_duration: str | None = None + inherits_project_auto_destroy: bool | None = None + description: str | None = None + execution_mode: ExecutionMode | None = None + file_triggers_enabled: bool | None = None + global_remote_state: bool | None = None + migration_environment: str | None = None + operations: bool | None = None + queue_all_runs: bool | None = None + speculative_enabled: bool | None = None + source_name: str | None = None + source_url: str | None = None + structured_run_output_enabled: bool | None = None + terraform_version: str | None = None + trigger_prefixes: list[str] = Field(default_factory=list) + trigger_patterns: list[str] = Field(default_factory=list) + vcs_repo: VCSRepo | None = None + working_directory: str | None = None + hyok_enabled: bool | None = None + tags: list[Tag] = Field(default_factory=list) + setting_overwrites: WorkspaceSettingOverwrites | None = None + project: Project | None = None + tag_bindings: list[TagBinding] = Field(default_factory=list) + + +class WorkspaceUpdateOptions(BaseModel): + name: str + type: str = "workspaces" + agent_pool_id: str | None = None + allow_destroy_plan: bool | None = None + assessments_enabled: bool | None = None + auto_apply: bool | None = None + auto_apply_run_trigger: bool | None = None + auto_destroy_at: datetime | None = None + auto_destroy_activity_duration: str | None = None + inherits_project_auto_destroy: bool | None = None + description: str | None = None + execution_mode: ExecutionMode | None = None + file_triggers_enabled: bool | None = None + global_remote_state: bool | None = None + operations: bool | None = None + queue_all_runs: bool | None = None + speculative_enabled: bool | None = None + structured_run_output_enabled: bool | None = None + terraform_version: str | None = None + trigger_prefixes: list[str] = Field(default_factory=list) + trigger_patterns: list[str] = Field(default_factory=list) + vcs_repo: VCSRepo | None = None + working_directory: str | None = None + hyok_enabled: bool | None = None + setting_overwrites: WorkspaceSettingOverwrites | None = None + project: Project | None = None + tag_bindings: list[TagBinding] = Field(default_factory=list) + + +class WorkspaceList(BaseModel): + items: list[Workspace] = Field(default_factory=list) + pagination: Pagination | None = None + + +class TagList(BaseModel): + items: list[Tag] = Field(default_factory=list) + pagination: Pagination | None = None + + +class WorkspaceRemoveVCSConnectionOptions(BaseModel): + """Options for removing VCS connection from a workspace.""" + + # Currently no options are defined, but this class can be extended in the future + id: str + vcs_repo: VCSRepo | None = None + + +class WorkspaceLockOptions(BaseModel): + """Options for locking a workspace.""" + + # Specifies the reason for locking the workspace. + reason: str + + +class WorkspaceAssignSSHKeyOptions(BaseModel): + """Options for assigning an SSH key to a workspace.""" + + ssh_key_id: str + type: str = "workspaces" + + +class workspaceUnassignSSHKeyOptions(BaseModel): + """Options for unassigning an SSH key from a workspace.""" + + # Must be nil to unset the currently assigned SSH key. + ssh_key_id: str + type: str = "workspaces" + + +class WorkspaceListRemoteStateConsumersOptions(BaseModel): + """Options for listing remote state consumers of a workspace.""" + + # Pagination options (from ListOptions) + page_number: int | None = None + page_size: int | None = None + + +class WorkspaceAddRemoteStateConsumersOptions(BaseModel): + """Options for adding remote state consumers to a workspace.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +class WorkspaceRemoveRemoteStateConsumersOptions(BaseModel): + """Options for removing remote state consumers from a workspace.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +class WorkspaceUpdateRemoteStateConsumersOptions(BaseModel): + """Options for updating remote state consumers of a workspace.""" + + workspaces: list[Workspace] = Field(default_factory=list) + + +class WorkspaceTagListOptions(BaseModel): + """Options for listing tags of a workspace.""" + + # Pagination options (from ListOptions) + page_number: int | None = None + page_size: int | None = None + query: str | None = None + + +class WorkspaceAddTagsOptions(BaseModel): + """Options for adding tags to a workspace.""" + + tags: list[Tag] = Field(default_factory=list) + + +class WorkspaceRemoveTagsOptions(BaseModel): + """Options for removing tags from a workspace.""" + + tags: list[Tag] = Field(default_factory=list) + + +class WorkspaceAddTagBindingsOptions(BaseModel): + """Options for adding tag bindings to a workspace.""" + + tag_bindings: list[TagBinding] = Field(default_factory=list) + + + +class VCSRepo(BaseModel): + branch: str | None = None + identifier: str | None = None + ingress_submodules: bool | None = None + oauth_token_id: str | None = None + tags_regex: str | None = None + gha_installation_id: str | None = None \ No newline at end of file diff --git a/src/pytfe/resources/organizations.py b/src/pytfe/resources/organizations.py index 95ec83e3..aa4e37f0 100644 --- a/src/pytfe/resources/organizations.py +++ b/src/pytfe/resources/organizations.py @@ -9,7 +9,7 @@ ERR_REQUIRED_EMAIL, ERR_REQUIRED_NAME, ) -from ..types import ( +from ..models.organization import ( Capacity, DataRetentionPolicy, DataRetentionPolicyChoice, @@ -167,7 +167,8 @@ def read_run_queue( ) data = r.json() - from ..types import Pagination, Run, RunStatus + + from ..models.organization import Pagination, Run, RunStatus runs = [] for item in data.get("data", []): diff --git a/src/pytfe/resources/projects.py b/src/pytfe/resources/projects.py index 1bd02d57..10046cc7 100644 --- a/src/pytfe/resources/projects.py +++ b/src/pytfe/resources/projects.py @@ -5,13 +5,18 @@ from collections.abc import Iterator from typing import Any -from ..types import ( - EffectiveTagBinding, +from ..models.project import ( + Project, ProjectAddTagBindingsOptions, ProjectCreateOptions, ProjectListOptions, ProjectUpdateOptions, + +) + +from ..models.common import ( + EffectiveTagBinding, TagBinding, ) from ..utils import valid_string, valid_string_id diff --git a/src/pytfe/resources/variable.py b/src/pytfe/resources/variable.py index b6751f50..6eff36d7 100644 --- a/src/pytfe/resources/variable.py +++ b/src/pytfe/resources/variable.py @@ -9,7 +9,7 @@ ERR_REQUIRED_CATEGORY, ERR_REQUIRED_KEY, ) -from ..types import ( +from ..models.variable import ( Variable, VariableCreateOptions, VariableListOptions, diff --git a/src/pytfe/resources/variable_sets.py b/src/pytfe/resources/variable_sets.py index 9dbb2f6b..357ccd14 100644 --- a/src/pytfe/resources/variable_sets.py +++ b/src/pytfe/resources/variable_sets.py @@ -5,7 +5,7 @@ from .._http import HTTPTransport from ._base import _Service -from ..types import ( +from ..models.variable_set import ( VariableSet, VariableSetApplyToProjectsOptions, VariableSetApplyToWorkspacesOptions, diff --git a/src/pytfe/resources/workspaces.py b/src/pytfe/resources/workspaces.py index dbfdf5e3..4781154e 100644 --- a/src/pytfe/resources/workspaces.py +++ b/src/pytfe/resources/workspaces.py @@ -16,7 +16,8 @@ WorkspaceMinimumLimitError, WorkspaceRequiredError, ) -from ..types import ( + +from ..types. import ( DataRetentionPolicy, DataRetentionPolicyChoice, DataRetentionPolicyDeleteOlder, @@ -24,11 +25,21 @@ DataRetentionPolicyDontDelete, DataRetentionPolicyDontDeleteSetOptions, DataRetentionPolicySetOptions, + + +) + +from ..models.common import ( + Tag, + TagBinding, EffectiveTagBinding, +) + +from ..models.workspace import ( + ExecutionMode, LockedByChoice, - Tag, - TagBinding, + VCSRepo, Workspace, WorkspaceActions, @@ -52,6 +63,7 @@ WorkspaceUpdateOptions, WorkspaceUpdateRemoteStateConsumersOptions, ) + from ..utils import ( valid_string, valid_string_id, diff --git a/src/pytfe/types.py b/src/pytfe/types.py deleted file mode 100644 index 2f0a4f9f..00000000 --- a/src/pytfe/types.py +++ /dev/null @@ -1,810 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -from enum import Enum -from typing import Any - -from pydantic import BaseModel, Field - - -class OrganizationUpdateOptions(BaseModel): - name: str | None = None - email: str | None = None - assessments_enforced: bool | None = None - collaborator_auth_policy: str | None = None - cost_estimation_enabled: bool | None = None - default_execution_mode: str | None = None - external_id: str | None = None - is_unified: bool | None = None - owners_team_saml_role_id: str | None = None - permissions: dict | None = None - saml_enabled: bool | None = None - session_remember: int | None = None - session_timeout: int | None = None - two_factor_conformant: bool | None = None - send_passing_statuses_for_untriggered_speculative_plans: bool | None = None - remaining_testable_count: int | None = None - speculative_plan_management_enabled: bool | None = None - aggregated_commit_status_enabled: bool | None = None - allow_force_delete_workspaces: bool | None = None - default_project: dict | None = None - default_agent_pool: dict | None = None - data_retention_policy: dict | None = None - data_retention_policy_choice: dict | None = None - - -class OrganizationCreateOptions(BaseModel): - name: str | None = None - email: str | None = None - assessments_enforced: bool | None = None - collaborator_auth_policy: str | None = None - cost_estimation_enabled: bool | None = None - default_execution_mode: str | None = None - external_id: str | None = None - is_unified: bool | None = None - owners_team_saml_role_id: str | None = None - permissions: dict | None = None - saml_enabled: bool | None = None - session_remember: int | None = None - session_timeout: int | None = None - two_factor_conformant: bool | None = None - send_passing_statuses_for_untriggered_speculative_plans: bool | None = None - remaining_testable_count: int | None = None - speculative_plan_management_enabled: bool | None = None - aggregated_commit_status_enabled: bool | None = None - allow_force_delete_workspaces: bool | None = None - default_project: dict | None = None - default_agent_pool: dict | None = None - data_retention_policy: dict | None = None - data_retention_policy_choice: dict | None = None - - -class ExecutionMode(str, Enum): - REMOTE = "remote" - AGENT = "agent" - LOCAL = "local" - - -class RunStatus(str, Enum): - PLANNING = "planning" - PLANNED = "planned" - APPLIED = "applied" - CANCELED = "canceled" - ERRORED = "errored" - - -class Organization(BaseModel): - name: str | None = None - assessments_enforced: bool | None = None - collaborator_auth_policy: str | None = None - cost_estimation_enabled: bool | None = None - created_at: datetime | None = None - default_execution_mode: str | None = None - email: str | None = None - external_id: str | None = None - id: str | None = None - is_unified: bool | None = None - owners_team_saml_role_id: str | None = None - permissions: dict | None = None - saml_enabled: bool | None = None - session_remember: int | None = None - session_timeout: int | None = None - trial_expires_at: datetime | None = None - two_factor_conformant: bool | None = None - send_passing_statuses_for_untriggered_speculative_plans: bool | None = None - remaining_testable_count: int | None = None - speculative_plan_management_enabled: bool | None = None - aggregated_commit_status_enabled: bool | None = None - allow_force_delete_workspaces: bool | None = None - default_project: dict | None = None - default_agent_pool: dict | None = None - data_retention_policy: dict | None = None - 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 - organization: str | None = None - execution_mode: ExecutionMode | None = None - project_id: str | None = None - - # Core attributes - actions: WorkspaceActions | None = None - allow_destroy_plan: bool = False - assessments_enabled: bool = False - auto_apply: bool = False - auto_apply_run_trigger: bool = False - auto_destroy_at: datetime | None = None - auto_destroy_activity_duration: str | None = None - can_queue_destroy_plan: bool = False - created_at: datetime | None = None - description: str = "" - environment: str = "" - file_triggers_enabled: bool = False - global_remote_state: bool = False - inherits_project_auto_destroy: bool = False - locked: bool = False - migration_environment: str = "" - no_code_upgrade_available: bool = False - operations: bool = False - permissions: WorkspacePermissions | None = None - queue_all_runs: bool = False - speculative_enabled: bool = False - source: WorkspaceSource | None = None - source_name: str = "" - source_url: str = "" - structured_run_output_enabled: bool = False - terraform_version: str = "" - trigger_prefixes: list[str] = Field(default_factory=list) - trigger_patterns: list[str] = Field(default_factory=list) - vcs_repo: VCSRepo | None = None - working_directory: str = "" - updated_at: datetime | None = None - resource_count: int = 0 - apply_duration_average: float | None = None # in seconds - plan_duration_average: float | None = None # in seconds - policy_check_failures: int = 0 - run_failures: int = 0 - runs_count: int = 0 - tag_names: list[str] = Field(default_factory=list) - setting_overwrites: WorkspaceSettingOverwrites | None = None - - # Relations - 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 - ssh_key: Any | None = None # SSHKey object - outputs: list[WorkspaceOutputs] = Field(default_factory=list) - tags: list[Tag] = Field(default_factory=list) - current_configuration_version: Any | None = None # ConfigurationVersion object - locked_by: LockedByChoice | None = None - variables: list[Any] = Field(default_factory=list) # Variable objects - tag_bindings: list[TagBinding] = Field(default_factory=list) - effective_tag_bindings: list[EffectiveTagBinding] = Field(default_factory=list) - - # Links - links: dict[str, Any] = Field(default_factory=dict) - data_retention_policy: DataRetentionPolicy | None = None - data_retention_policy_choice: DataRetentionPolicyChoice | None = None - - -class Capacity(BaseModel): - organization: str - pending: int - running: int - - -class Entitlements(BaseModel): - id: str - agents: bool | None = None - audit_logging: bool | None = None - cost_estimation: bool | None = None - global_run_tasks: bool | None = None - operations: bool | None = None - private_module_registry: bool | None = None - private_run_tasks: bool | None = None - run_tasks: bool | None = None - sso: bool | None = None - sentinel: bool | None = None - state_storage: bool | None = None - teams: bool | None = None - vcs_integrations: bool | None = None - waypoint_actions: bool | None = None - waypoint_templates_and_addons: bool | None = None - - -class Run(BaseModel): - id: str - status: RunStatus - # Add other Run fields as needed - - -class Pagination(BaseModel): - current_page: int - total_count: int - previous_page: int | None = None - next_page: int | None = None - total_pages: int | None = None - # Add other pagination fields as needed - - -class RunQueue(BaseModel): - pagination: Pagination | None = None - items: list[Run] = Field(default_factory=list) - - -class ReadRunQueueOptions(BaseModel): - # List options for pagination - page_number: int | None = None - page_size: int | None = None - - -class DataRetentionPolicy(BaseModel): - """Deprecated: Use DataRetentionPolicyDeleteOlder instead.""" - - id: str - delete_older_than_n_days: int - - -class DataRetentionPolicyDeleteOlder(BaseModel): - id: str - delete_older_than_n_days: int - - -class DataRetentionPolicyDontDelete(BaseModel): - id: str - - -class DataRetentionPolicyChoice(BaseModel): - """Polymorphic data retention policy choice.""" - - data_retention_policy: DataRetentionPolicy | None = None - data_retention_policy_delete_older: DataRetentionPolicyDeleteOlder | None = None - data_retention_policy_dont_delete: DataRetentionPolicyDontDelete | None = None - - def is_populated(self) -> bool: - """Returns whether one of the choices is populated.""" - return ( - self.data_retention_policy is not None - or self.data_retention_policy_delete_older is not None - or self.data_retention_policy_dont_delete is not None - ) - - def convert_to_legacy_struct(self) -> DataRetentionPolicy | None: - """Convert the DataRetentionPolicyChoice to the legacy DataRetentionPolicy struct.""" - if not self.is_populated(): - return None - - if self.data_retention_policy is not None: - return self.data_retention_policy - elif self.data_retention_policy_delete_older is not None: - return DataRetentionPolicy( - id=self.data_retention_policy_delete_older.id, - delete_older_than_n_days=self.data_retention_policy_delete_older.delete_older_than_n_days, - ) - return None - - -class DataRetentionPolicySetOptions(BaseModel): - """Deprecated: Use DataRetentionPolicyDeleteOlderSetOptions instead.""" - - delete_older_than_n_days: int - - -class DataRetentionPolicyDeleteOlderSetOptions(BaseModel): - delete_older_than_n_days: int - - -class DataRetentionPolicyDontDeleteSetOptions(BaseModel): - pass # No additional fields needed - - -# Variables related models -class CategoryType(str, Enum): - ENV = "env" - POLICY_SET = "policy-set" - TERRAFORM = "terraform" - - -class Variable(BaseModel): - id: str | None = None - key: str | None = None - value: str | None = None - description: str | None = None - category: CategoryType | None = None - hcl: bool | None = None - sensitive: bool | None = None - version_id: str | None = None - workspace: dict | None = None - - -class VariableListOptions(BaseModel): - # Base pagination options would be handled by the service layer - pass - - -class VariableCreateOptions(BaseModel): - key: str | None = None - value: str | None = None - description: str | None = None - category: CategoryType | None = None - hcl: bool | None = None - sensitive: bool | None = None - - -class VariableUpdateOptions(BaseModel): - key: str | None = None - value: str | None = None - description: str | None = None - category: CategoryType | None = None - hcl: bool | None = None - sensitive: bool | None = None - - -class Tag(BaseModel): - id: str | None = None - name: str = "" - - -class TagBinding(BaseModel): - id: str | None = None - key: str - value: str | None = None - - -class EffectiveTagBinding(BaseModel): - id: str - key: str - value: str | None = None - links: dict[str, Any] = Field(default_factory=dict) - - -class WorkspaceIncludeOpt(str, Enum): - ORGANIZATION = "organization" - CURRENT_CONFIG_VER = "current_configuration_version" - CURRENT_CONFIG_VER_INGRESS = "current_configuration_version.ingress_attributes" - CURRENT_RUN = "current_run" - CURRENT_RUN_PLAN = "current_run.plan" - CURRENT_RUN_CONFIG_VER = "current_run.configuration_version" - CURRENT_RUN_CONFIG_VER_INGRESS = ( - "current_run.configuration_version.ingress_attributes" - ) - EFFECTIVE_TAG_BINDINGS = "effective_tag_bindings" - LOCKED_BY = "locked_by" - README = "readme" - OUTPUTS = "outputs" - CURRENT_STATE_VER = "current-state-version" - PROJECT = "project" - - -class VCSRepo(BaseModel): - branch: str | None = None - identifier: str | None = None - ingress_submodules: bool | None = None - oauth_token_id: str | None = None - tags_regex: str | None = None - gha_installation_id: str | None = None - - -class WorkspaceSource(str, Enum): - API = "tfe-api" - MODULE = "tfe-module" - UI = "tfe-ui" - TERRAFORM = "terraform" - - -class WorkspaceActions(BaseModel): - is_destroyable: bool = False - - -class WorkspacePermissions(BaseModel): - can_destroy: bool = False - can_force_unlock: bool = False - can_lock: bool = False - can_manage_run_tasks: bool = False - can_queue_apply: bool = False - can_queue_destroy: bool = False - can_queue_run: bool = False - can_read_settings: bool = False - can_unlock: bool = False - can_update: bool = False - can_update_variable: bool = False - can_force_delete: bool | None = None - - -class WorkspaceSettingOverwrites(BaseModel): - execution_mode: bool | None = None - agent_pool: bool | None = None - - -class WorkspaceOutputs(BaseModel): - id: str - name: str - sensitive: bool = False - output_type: str - value: Any | None = None - - -class LockedByChoice(BaseModel): - run: Any | None = None - user: Any | None = None - team: Any | None = None - - -class WorkspaceListOptions(BaseModel): - """Options for listing workspaces.""" - - # Pagination options (from ListOptions) - page_number: int | None = None - page_size: int | None = None - - # Search and filter options - search: str | None = None # search[name] - partial workspace name - tags: str | None = None # search[tags] - comma-separated tag names - exclude_tags: str | None = ( - None # search[exclude-tags] - comma-separated tag names to exclude - ) - wildcard_name: str | None = None # search[wildcard-name] - substring matching - project_id: str | None = None # filter[project][id] - project ID filter - current_run_status: str | None = ( - None # filter[current-run][status] - run status filter - ) - - # Tag binding filters (not URL encoded, handled specially) - tag_bindings: list[TagBinding] = Field(default_factory=list) - - # Include related resources - include: list[WorkspaceIncludeOpt] = Field(default_factory=list) - - # Sorting options - sort: str | None = ( - None # "name" (default) or "current-run.created-at", prepend "-" to reverse - ) - - -class WorkspaceReadOptions(BaseModel): - include: list[WorkspaceIncludeOpt] = Field(default_factory=list) - - -class WorkspaceCreateOptions(BaseModel): - name: str - type: str = "workspaces" - agent_pool_id: str | None = None - allow_destroy_plan: bool | None = None - assessments_enabled: bool | None = None - auto_apply: bool | None = None - auto_apply_run_trigger: bool | None = None - auto_destroy_at: datetime | None = None - auto_destroy_activity_duration: str | None = None - inherits_project_auto_destroy: bool | None = None - description: str | None = None - execution_mode: ExecutionMode | None = None - file_triggers_enabled: bool | None = None - global_remote_state: bool | None = None - migration_environment: str | None = None - operations: bool | None = None - queue_all_runs: bool | None = None - speculative_enabled: bool | None = None - source_name: str | None = None - source_url: str | None = None - structured_run_output_enabled: bool | None = None - terraform_version: str | None = None - trigger_prefixes: list[str] = Field(default_factory=list) - trigger_patterns: list[str] = Field(default_factory=list) - vcs_repo: VCSRepo | None = None - working_directory: str | None = None - hyok_enabled: bool | None = None - tags: list[Tag] = Field(default_factory=list) - setting_overwrites: WorkspaceSettingOverwrites | None = None - project: Project | None = None - tag_bindings: list[TagBinding] = Field(default_factory=list) - - -class WorkspaceUpdateOptions(BaseModel): - name: str - type: str = "workspaces" - agent_pool_id: str | None = None - allow_destroy_plan: bool | None = None - assessments_enabled: bool | None = None - auto_apply: bool | None = None - auto_apply_run_trigger: bool | None = None - auto_destroy_at: datetime | None = None - auto_destroy_activity_duration: str | None = None - inherits_project_auto_destroy: bool | None = None - description: str | None = None - execution_mode: ExecutionMode | None = None - file_triggers_enabled: bool | None = None - global_remote_state: bool | None = None - operations: bool | None = None - queue_all_runs: bool | None = None - speculative_enabled: bool | None = None - structured_run_output_enabled: bool | None = None - terraform_version: str | None = None - trigger_prefixes: list[str] = Field(default_factory=list) - trigger_patterns: list[str] = Field(default_factory=list) - vcs_repo: VCSRepo | None = None - working_directory: str | None = None - hyok_enabled: bool | None = None - setting_overwrites: WorkspaceSettingOverwrites | None = None - project: Project | None = None - tag_bindings: list[TagBinding] = Field(default_factory=list) - - -class WorkspaceList(BaseModel): - items: list[Workspace] = Field(default_factory=list) - pagination: Pagination | None = None - - -class TagList(BaseModel): - items: list[Tag] = Field(default_factory=list) - pagination: Pagination | None = None - - -class WorkspaceRemoveVCSConnectionOptions(BaseModel): - """Options for removing VCS connection from a workspace.""" - - # Currently no options are defined, but this class can be extended in the future - id: str - vcs_repo: VCSRepo | None = None - - -class WorkspaceLockOptions(BaseModel): - """Options for locking a workspace.""" - - # Specifies the reason for locking the workspace. - reason: str - - -class WorkspaceAssignSSHKeyOptions(BaseModel): - """Options for assigning an SSH key to a workspace.""" - - ssh_key_id: str - type: str = "workspaces" - - -class workspaceUnassignSSHKeyOptions(BaseModel): - """Options for unassigning an SSH key from a workspace.""" - - # Must be nil to unset the currently assigned SSH key. - ssh_key_id: str - type: str = "workspaces" - - -class WorkspaceListRemoteStateConsumersOptions(BaseModel): - """Options for listing remote state consumers of a workspace.""" - - # Pagination options (from ListOptions) - page_number: int | None = None - page_size: int | None = None - - -class WorkspaceAddRemoteStateConsumersOptions(BaseModel): - """Options for adding remote state consumers to a workspace.""" - - workspaces: list[Workspace] = Field(default_factory=list) - - -class WorkspaceRemoveRemoteStateConsumersOptions(BaseModel): - """Options for removing remote state consumers from a workspace.""" - - workspaces: list[Workspace] = Field(default_factory=list) - - -class WorkspaceUpdateRemoteStateConsumersOptions(BaseModel): - """Options for updating remote state consumers of a workspace.""" - - workspaces: list[Workspace] = Field(default_factory=list) - - -class WorkspaceTagListOptions(BaseModel): - """Options for listing tags of a workspace.""" - - # Pagination options (from ListOptions) - page_number: int | None = None - page_size: int | None = None - query: str | None = None - - -class WorkspaceAddTagsOptions(BaseModel): - """Options for adding tags to a workspace.""" - - tags: list[Tag] = Field(default_factory=list) - - -class WorkspaceRemoveTagsOptions(BaseModel): - """Options for removing tags from a workspace.""" - - tags: list[Tag] = Field(default_factory=list) - - -class WorkspaceAddTagBindingsOptions(BaseModel): - """Options for adding tag bindings to a workspace.""" - - tag_bindings: list[TagBinding] = Field(default_factory=list) - - -# 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 From 47bedddc29165e622fadc8b691f1ece0ce7af0b5 Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Thu, 9 Oct 2025 15:58:30 +0530 Subject: [PATCH 4/9] update readme and other changes --- examples/agent_pool.py | 1 + src/pytfe/_http.py | 7 ++++--- src/pytfe/models/common.py | 2 +- src/pytfe/models/organization.py | 1 - src/pytfe/models/project.py | 2 -- src/pytfe/models/variable.py | 3 ++- src/pytfe/models/variable_set.py | 2 +- src/pytfe/models/workspace.py | 10 ++++------ src/pytfe/resources/organizations.py | 1 - src/pytfe/resources/projects.py | 2 -- src/pytfe/resources/workspaces.py | 6 +----- src/pytfe/utils.py | 4 +++- 12 files changed, 17 insertions(+), 24 deletions(-) diff --git a/examples/agent_pool.py b/examples/agent_pool.py index 71e920f2..1b6e15b1 100644 --- a/examples/agent_pool.py +++ b/examples/agent_pool.py @@ -30,6 +30,7 @@ AgentTokenCreateOptions, ) + def main(): """Main function demonstrating agent pool operations.""" # Get environment variables diff --git a/src/pytfe/_http.py b/src/pytfe/_http.py index 1c82d41b..5c44e1e9 100644 --- a/src/pytfe/_http.py +++ b/src/pytfe/_http.py @@ -53,7 +53,10 @@ def __init__( self.proxies = proxies self.ca_bundle = ca_bundle self._sync = httpx.Client( - http2=http2, timeout=timeout, verify=ca_bundle or verify_tls, proxies=proxies + http2=http2, + timeout=timeout, + verify=ca_bundle or verify_tls, + proxies=proxies, ) # proxies=proxies def _build_url(self, path: str) -> str: @@ -105,7 +108,6 @@ def request( self._raise_if_error(resp) return resp - def _sleep(self, attempt: int, retry_after: float | None) -> None: if retry_after is not None: time.sleep(retry_after) @@ -113,7 +115,6 @@ def _sleep(self, attempt: int, retry_after: float | None) -> None: delay = min(self.backoff_cap, self.backoff_base * (2**attempt)) time.sleep(delay) - def _raise_if_error(self, resp: httpx.Response) -> None: status = resp.status_code diff --git a/src/pytfe/models/common.py b/src/pytfe/models/common.py index 80dfcef0..c8f7e820 100644 --- a/src/pytfe/models/common.py +++ b/src/pytfe/models/common.py @@ -31,4 +31,4 @@ class EffectiveTagBinding(BaseModel): id: str key: str value: str | None = None - links: dict[str, Any] = Field(default_factory=dict) \ No newline at end of file + links: dict[str, Any] = Field(default_factory=dict) diff --git a/src/pytfe/models/organization.py b/src/pytfe/models/organization.py index 8c810cd6..06d544d6 100644 --- a/src/pytfe/models/organization.py +++ b/src/pytfe/models/organization.py @@ -101,7 +101,6 @@ class Organization(BaseModel): data_retention_policy_choice: dict | None = None - class Capacity(BaseModel): organization: str pending: int diff --git a/src/pytfe/models/project.py b/src/pytfe/models/project.py index de1627d5..405a77f6 100644 --- a/src/pytfe/models/project.py +++ b/src/pytfe/models/project.py @@ -56,5 +56,3 @@ class ProjectAddTagBindingsOptions(BaseModel): """Options for adding tag bindings to a project""" tag_bindings: list[TagBinding] = Field(default_factory=list) - - diff --git a/src/pytfe/models/variable.py b/src/pytfe/models/variable.py index 0953721f..a50660ec 100644 --- a/src/pytfe/models/variable.py +++ b/src/pytfe/models/variable.py @@ -1,4 +1,3 @@ - from __future__ import annotations from enum import Enum @@ -23,10 +22,12 @@ class Variable(BaseModel): version_id: str | None = None workspace: dict | None = None + class VariableListOptions(BaseModel): # Base pagination options would be handled by the service layer pass + class VariableCreateOptions(BaseModel): key: str | None = None value: str | None = None diff --git a/src/pytfe/models/variable_set.py b/src/pytfe/models/variable_set.py index 3a761cde..9bd9b976 100644 --- a/src/pytfe/models/variable_set.py +++ b/src/pytfe/models/variable_set.py @@ -160,4 +160,4 @@ class VariableSetVariableUpdateOptions(BaseModel): value: str | None = None description: str | None = None hcl: bool | None = None - sensitive: bool | None = None \ No newline at end of file + sensitive: bool | None = None diff --git a/src/pytfe/models/workspace.py b/src/pytfe/models/workspace.py index 5b316498..2c70b05b 100644 --- a/src/pytfe/models/workspace.py +++ b/src/pytfe/models/workspace.py @@ -12,7 +12,6 @@ from .organization import DataRetentionPolicy, DataRetentionPolicyChoice - class Workspace(BaseModel): id: str name: str | None = None @@ -69,7 +68,7 @@ class Workspace(BaseModel): ssh_key: Any | None = None # SSHKey object outputs: list[WorkspaceOutputs] = Field(default_factory=list) tags: list[str] = Field(default_factory=list) - #tags: list[Tag] = Field(default_factory=list) + # tags: list[Tag] = Field(default_factory=list) current_configuration_version: Any | None = None # ConfigurationVersion object locked_by: LockedByChoice | None = None variables: list[Any] = Field(default_factory=list) # Variable objects @@ -99,12 +98,14 @@ class WorkspaceIncludeOpt(str, Enum): CURRENT_STATE_VER = "current-state-version" PROJECT = "project" + class WorkspaceSource(str, Enum): API = "tfe-api" MODULE = "tfe-module" UI = "tfe-ui" TERRAFORM = "terraform" + class WorkspaceActions(BaseModel): is_destroyable: bool = False @@ -137,8 +138,6 @@ class WorkspaceOutputs(BaseModel): value: Any | None = None - - class LockedByChoice(BaseModel): run: Any | None = None user: Any | None = None @@ -337,11 +336,10 @@ class WorkspaceAddTagBindingsOptions(BaseModel): tag_bindings: list[TagBinding] = Field(default_factory=list) - class VCSRepo(BaseModel): branch: str | None = None identifier: str | None = None ingress_submodules: bool | None = None oauth_token_id: str | None = None tags_regex: str | None = None - gha_installation_id: str | None = None \ No newline at end of file + gha_installation_id: str | None = None diff --git a/src/pytfe/resources/organizations.py b/src/pytfe/resources/organizations.py index aa4e37f0..78977fee 100644 --- a/src/pytfe/resources/organizations.py +++ b/src/pytfe/resources/organizations.py @@ -167,7 +167,6 @@ def read_run_queue( ) data = r.json() - from ..models.organization import Pagination, Run, RunStatus runs = [] diff --git a/src/pytfe/resources/projects.py b/src/pytfe/resources/projects.py index 10046cc7..3cf9fcf7 100644 --- a/src/pytfe/resources/projects.py +++ b/src/pytfe/resources/projects.py @@ -6,13 +6,11 @@ from typing import Any from ..models.project import ( - Project, ProjectAddTagBindingsOptions, ProjectCreateOptions, ProjectListOptions, ProjectUpdateOptions, - ) from ..models.common import ( diff --git a/src/pytfe/resources/workspaces.py b/src/pytfe/resources/workspaces.py index 4781154e..75215dfc 100644 --- a/src/pytfe/resources/workspaces.py +++ b/src/pytfe/resources/workspaces.py @@ -17,7 +17,7 @@ WorkspaceRequiredError, ) -from ..types. import ( +from ..models.organization import ( DataRetentionPolicy, DataRetentionPolicyChoice, DataRetentionPolicyDeleteOlder, @@ -25,8 +25,6 @@ DataRetentionPolicyDontDelete, DataRetentionPolicyDontDeleteSetOptions, DataRetentionPolicySetOptions, - - ) from ..models.common import ( @@ -36,10 +34,8 @@ ) from ..models.workspace import ( - ExecutionMode, LockedByChoice, - VCSRepo, Workspace, WorkspaceActions, diff --git a/src/pytfe/utils.py b/src/pytfe/utils.py index 71df5028..3d843459 100644 --- a/src/pytfe/utils.py +++ b/src/pytfe/utils.py @@ -284,6 +284,7 @@ def validate_oauth_client_remove_projects_options( if len(options.projects) == 0: raise ValueError(ERR_PROJECT_MIN_LIMIT) + def valid_project_name(name: str) -> bool: """Validate project name format""" if not valid_string(name): @@ -352,7 +353,8 @@ def validate_project_list_options( if name and not valid_project_name(name): raise ValueError("Project name must be valid") - + + def pack_contents(path: str) -> io.BytesIO: """ Pack directory contents into a tar.gz archive suitable for upload. From f9e37b3f20380fff013685b8bd6423042feb8230 Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Thu, 9 Oct 2025 16:12:52 +0530 Subject: [PATCH 5/9] lint fixes --- Makefile | 2 +- src/pytfe/_http.py | 6 +++--- src/pytfe/config.py | 2 +- src/pytfe/models/workspace.py | 2 +- src/pytfe/resources/admin/settings.py | 7 +------ src/pytfe/utils.py | 2 +- tests/units/test_workspaces.py | 20 ++++++++++++++++++-- 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 731afd8c..8eb56545 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: help fmt fmt-check lint check test install dev-install type-check clean all venv activate PYTHON := python3 -SRC_DIR := src/tfe +SRC_DIR := src/pytfe TEST_DIR := tests VENV := .venv VENV_PYTHON := $(VENV)/bin/python diff --git a/src/pytfe/_http.py b/src/pytfe/_http.py index 5c44e1e9..2c32012b 100644 --- a/src/pytfe/_http.py +++ b/src/pytfe/_http.py @@ -36,7 +36,7 @@ def __init__( backoff_cap: float, backoff_jitter: bool, http2: bool, - proxies: dict | None, + proxies: str | None, ca_bundle: str | None, ): self.base = address.rstrip("/") @@ -56,8 +56,8 @@ def __init__( http2=http2, timeout=timeout, verify=ca_bundle or verify_tls, - proxies=proxies, - ) # proxies=proxies + proxy=proxies, + ) def _build_url(self, path: str) -> str: # IMPORTANT: don't prefix absolute URLs (hosted_state, signed blobs, etc.) diff --git a/src/pytfe/config.py b/src/pytfe/config.py index 6a70e341..e4c3da33 100644 --- a/src/pytfe/config.py +++ b/src/pytfe/config.py @@ -22,7 +22,7 @@ class TFEConfig(BaseModel): backoff_cap: float = 8.0 backoff_jitter: bool = True http2: bool = True - proxies: dict[str, str] | None = None + proxies: str | None = None ca_bundle: str | None = os.getenv("SSL_CERT_FILE", None) @classmethod diff --git a/src/pytfe/models/workspace.py b/src/pytfe/models/workspace.py index 2c70b05b..19e3aea6 100644 --- a/src/pytfe/models/workspace.py +++ b/src/pytfe/models/workspace.py @@ -67,7 +67,7 @@ class Workspace(BaseModel): project: Project | None = None ssh_key: Any | None = None # SSHKey object outputs: list[WorkspaceOutputs] = Field(default_factory=list) - tags: list[str] = Field(default_factory=list) + tags: list[Tag] = Field(default_factory=list) # tags: list[Tag] = Field(default_factory=list) current_configuration_version: Any | None = None # ConfigurationVersion object locked_by: LockedByChoice | None = None diff --git a/src/pytfe/resources/admin/settings.py b/src/pytfe/resources/admin/settings.py index 44a654df..f2b7b556 100644 --- a/src/pytfe/resources/admin/settings.py +++ b/src/pytfe/resources/admin/settings.py @@ -2,7 +2,7 @@ from typing import Any -from .._base import _AService, _Service +from .._base import _Service class AdminSettings(_Service): @@ -10,8 +10,3 @@ def terraform_versions(self) -> Any: r = self.t.request("GET", "/api/v2/admin/terraform-versions") return r.json() - -class AdminSettingsAsync(_AService): - async def terraform_versions(self) -> Any: - r = await self.t.arequest("GET", "/api/v2/admin/terraform-versions") - return r.json() diff --git a/src/pytfe/utils.py b/src/pytfe/utils.py index 3d843459..9adf58d3 100644 --- a/src/pytfe/utils.py +++ b/src/pytfe/utils.py @@ -31,7 +31,7 @@ UnsupportedBothTriggerPatternsAndPrefixesError, UnsupportedOperationsError, ) -from .types import ( +from .models.workspace import ( VCSRepo, WorkspaceCreateOptions, WorkspaceUpdateOptions, diff --git a/tests/units/test_workspaces.py b/tests/units/test_workspaces.py index a9bcf07f..e3186c19 100644 --- a/tests/units/test_workspaces.py +++ b/tests/units/test_workspaces.py @@ -20,7 +20,23 @@ WorkspaceMinimumLimitError, ) from src.pytfe.resources.workspaces import Workspaces, _ws_from -from src.pytfe.types import ( + +from src.pytfe.models.organization import ( + DataRetentionPolicyDeleteOlderSetOptions, + DataRetentionPolicyDontDeleteSetOptions, + DataRetentionPolicySetOptions, + ExecutionMode, +) + +from src.pytfe.models.project import Project + +from src.pytfe.models.common import ( + EffectiveTagBinding, + Tag, + TagBinding, +) + +from src.pytfe.models.workspace import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, DataRetentionPolicySetOptions, @@ -233,7 +249,7 @@ def test_read_workspace_with_options( sample_workspace_response ) - from src.pytfe.types import WorkspaceIncludeOpt + from src.pytfe.models.workspace import WorkspaceIncludeOpt options = WorkspaceReadOptions( include=[WorkspaceIncludeOpt.CURRENT_RUN, WorkspaceIncludeOpt.OUTPUTS] From 72eed30f5642058c7d5fb1d5e006f10e2f8e4e63 Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Fri, 10 Oct 2025 05:56:49 +0530 Subject: [PATCH 6/9] Fixes tests and split models --- src/pytfe/models/__init__.py | 165 ++++++++++++------ src/pytfe/models/common.py | 4 + src/pytfe/models/configuration_version.py | 138 +++++++++++++-- .../models/configuration_version_types.py | 135 -------------- ...try_module_types.py => registry_module.py} | 9 - ...provider_types.py => registry_provider.py} | 0 src/pytfe/models/workspace.py | 5 - src/pytfe/resources/configuration_version.py | 2 +- src/pytfe/resources/registry_module.py | 2 +- src/pytfe/resources/registry_provider.py | 2 +- tests/units/test_apply.py | 4 +- tests/units/test_configuration_version.py | 6 +- tests/units/test_project.py | 6 +- tests/units/test_query_run.py | 2 +- tests/units/test_run_trigger.py | 2 +- tests/units/test_variable_sets.py | 2 +- tests/units/test_workspaces.py | 6 - 17 files changed, 259 insertions(+), 231 deletions(-) delete mode 100644 src/pytfe/models/configuration_version_types.py rename src/pytfe/models/{registry_module_types.py => registry_module.py} (94%) rename src/pytfe/models/{registry_provider_types.py => registry_provider.py} (100%) diff --git a/src/pytfe/models/__init__.py b/src/pytfe/models/__init__.py index 996b8f8d..94abf509 100644 --- a/src/pytfe/models/__init__.py +++ b/src/pytfe/models/__init__.py @@ -1,10 +1,6 @@ -"""Types package for TFE client.""" +from __future__ import annotations -# Import all types from the main types module by using importlib to avoid circular imports -import importlib.util -import os - -# Re-export all agent and agent pool types +# ── Agent & Agent Pools ──────────────────────────────────────────────────────── from .agent import ( Agent, AgentListOptions, @@ -23,8 +19,9 @@ AgentTokenListOptions, ) -# Re-export all configuration version types -from .configuration_version_types import ( +# ── Configuration Versions ──────────────────────────────────────────────────── +# (Old: .configuration_version_types) → import directly from real module +from .configuration_version import ( ConfigurationSource, ConfigurationStatus, ConfigurationVersion, @@ -37,7 +34,7 @@ IngressAttributes, ) -# Re-export all OAuth client types +# ── OAuth ───────────────────────────────────────────────────────────────────── from .oauth_client import ( OAuthClient, OAuthClientAddProjectsOptions, @@ -51,7 +48,6 @@ ServiceProviderType, ) -# Re-export all OAuth token types from .oauth_token import ( OAuthToken, OAuthTokenList, @@ -59,7 +55,7 @@ OAuthTokenUpdateOptions, ) -# Re-export all query run types +# ── Query Runs ──────────────────────────────────────────────────────────────── from .query_run import ( QueryRun, QueryRunCancelOptions, @@ -74,8 +70,9 @@ QueryRunType, ) -# Re-export all registry module types -from .registry_module_types import ( +# ── Registry Modules / Providers ────────────────────────────────────────────── +# (Old: .registry_module_types / .registry_provider_types) → import from real modules +from .registry_module import ( AgentExecutionMode, Commit, CommitList, @@ -107,8 +104,7 @@ TestConfig, ) -# Re-export all registry provider types -from .registry_provider_types import ( +from .registry_provider import ( RegistryProvider, RegistryProviderCreateOptions, RegistryProviderID, @@ -119,7 +115,7 @@ RegistryProviderReadOptions, ) -# Re-export all reserved tag key types +# ── Reserved Tag Keys ───────────────────────────────────────────────────────── from .reserved_tag_key import ( ReservedTagKey, ReservedTagKeyCreateOptions, @@ -128,7 +124,7 @@ ReservedTagKeyUpdateOptions, ) -# Re-export all SSH key types +# ── SSH Keys ────────────────────────────────────────────────────────────────── from .ssh_key import ( SSHKey, SSHKeyCreateOptions, @@ -137,9 +133,85 @@ SSHKeyUpdateOptions, ) -# Define what should be available when importing with * +# ── Core models split out of old types.py ───────────────────────────────────── +# Adjust these imports to match where you placed them during the split. + +# Common / pagination / enums +from .common import Pagination, Tag, TagBinding, EffectiveTagBinding, TagList # if you put ExecutionMode enum here +from .organization import Entitlements, ExecutionMode + +# Organization / Project +from .organization import ( + Organization, + OrganizationCreateOptions, + OrganizationUpdateOptions, +) + +from .project import Project + +# Data retention policy family +from .organization import ( + DataRetentionPolicy, + DataRetentionPolicyChoice, + DataRetentionPolicyDeleteOlder, + DataRetentionPolicyDeleteOlderSetOptions, + DataRetentionPolicyDontDelete, + DataRetentionPolicyDontDeleteSetOptions, + DataRetentionPolicySetOptions, +) + + +# Variables +from .variable import ( + Variable, + VariableCreateOptions, + VariableListOptions, + VariableUpdateOptions, +) + +# Workspaces +from .workspace import ( + LockedByChoice, + VCSRepo, + Workspace, + WorkspaceActions, + WorkspaceAddRemoteStateConsumersOptions, + WorkspaceAddTagBindingsOptions, + WorkspaceAddTagsOptions, + WorkspaceAssignSSHKeyOptions, + WorkspaceCreateOptions, + WorkspaceIncludeOpt, + WorkspaceList, + WorkspaceListOptions, + WorkspaceListRemoteStateConsumersOptions, + WorkspaceLockOptions, + WorkspaceOutputs, + WorkspacePermissions, + WorkspaceReadOptions, + WorkspaceRemoveRemoteStateConsumersOptions, + WorkspaceRemoveTagsOptions, + WorkspaceRemoveVCSConnectionOptions, + WorkspaceSettingOverwrites, + WorkspaceSource, + WorkspaceTagListOptions, + WorkspaceUpdateOptions, + WorkspaceUpdateRemoteStateConsumersOptions, +) + +# Runs +from .run import ( + Run, + RunStatus, +) + +from .organization import ( + RunQueue, + ReadRunQueueOptions, +) + +# ── Public surface ──────────────────────────────────────────────────────────── __all__ = [ - # OAuth client types + # OAuth "OAuthClient", "OAuthClientAddProjectsOptions", "OAuthClientCreateOptions", @@ -150,24 +222,24 @@ "OAuthClientRemoveProjectsOptions", "OAuthClientUpdateOptions", "ServiceProviderType", - # OAuth token types + # OAuth token "OAuthToken", "OAuthTokenList", "OAuthTokenListOptions", "OAuthTokenUpdateOptions", - # SSH key types + # SSH keys "SSHKey", "SSHKeyCreateOptions", "SSHKeyList", "SSHKeyListOptions", "SSHKeyUpdateOptions", - # Reserved tag key types + # Reserved tag keys "ReservedTagKey", "ReservedTagKeyCreateOptions", "ReservedTagKeyList", "ReservedTagKeyListOptions", "ReservedTagKeyUpdateOptions", - # Agent and agent pool types + # Agent & pools "Agent", "AgentPool", "AgentPoolAllowedWorkspacePolicy", @@ -183,7 +255,7 @@ "AgentToken", "AgentTokenCreateOptions", "AgentTokenListOptions", - # Configuration version types + # Configuration versions "ConfigurationSource", "ConfigurationStatus", "ConfigurationVersion", @@ -194,7 +266,7 @@ "ConfigurationVersionUpload", "ConfigVerIncludeOpt", "IngressAttributes", - # Registry module types + # Registry modules "AgentExecutionMode", "Commit", "CommitList", @@ -224,7 +296,7 @@ "Root", "TestConfig", "TerraformRegistryModule", - # Registry provider types + # Registry providers "RegistryProvider", "RegistryProviderCreateOptions", "RegistryProviderID", @@ -233,7 +305,7 @@ "RegistryProviderListOptions", "RegistryProviderPermissions", "RegistryProviderReadOptions", - # Query run types + # Query runs "QueryRun", "QueryRunCancelOptions", "QueryRunCreateOptions", @@ -245,8 +317,14 @@ "QueryRunResults", "QueryRunStatus", "QueryRunType", - # Main types from types.py (will be dynamically added below) - "Capacity", + # Core (from old types.py, now split) + "Entitlements", + "ExecutionMode", + "Pagination", + "Organization", + "OrganizationCreateOptions", + "OrganizationUpdateOptions", + "Project", "DataRetentionPolicy", "DataRetentionPolicyChoice", "DataRetentionPolicyDeleteOlder", @@ -255,18 +333,6 @@ "DataRetentionPolicyDontDeleteSetOptions", "DataRetentionPolicySetOptions", "EffectiveTagBinding", - "Entitlements", - "ExecutionMode", - "LockedByChoice", - "Organization", - "OrganizationCreateOptions", - "OrganizationUpdateOptions", - "Pagination", - "Project", - "ReadRunQueueOptions", - "Run", - "RunQueue", - "RunStatus", "Tag", "TagBinding", "TagList", @@ -274,6 +340,7 @@ "VariableCreateOptions", "VariableListOptions", "VariableUpdateOptions", + "LockedByChoice", "VCSRepo", "Workspace", "WorkspaceActions", @@ -298,16 +365,8 @@ "WorkspaceTagListOptions", "WorkspaceUpdateOptions", "WorkspaceUpdateRemoteStateConsumersOptions", + "Run", + "RunQueue", + "RunStatus", + "ReadRunQueueOptions", ] - -# Load the main types.py file that's at the same level as this types/ directory -types_py_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "types.py") -spec = importlib.util.spec_from_file_location("main_types", types_py_path) -if spec is not None and spec.loader is not None: - main_types = importlib.util.module_from_spec(spec) - spec.loader.exec_module(main_types) - - # Re-export all main types - for name in dir(main_types): - if not name.startswith("_"): - globals()[name] = getattr(main_types, name) diff --git a/src/pytfe/models/common.py b/src/pytfe/models/common.py index c8f7e820..829ee312 100644 --- a/src/pytfe/models/common.py +++ b/src/pytfe/models/common.py @@ -26,6 +26,10 @@ class TagBinding(BaseModel): key: str value: str | None = None +class TagList(BaseModel): + items: list[Tag] = Field(default_factory=list) + pagination: Pagination | None = None + class EffectiveTagBinding(BaseModel): id: str diff --git a/src/pytfe/models/configuration_version.py b/src/pytfe/models/configuration_version.py index a3e3023f..6bc613a8 100644 --- a/src/pytfe/models/configuration_version.py +++ b/src/pytfe/models/configuration_version.py @@ -1,17 +1,135 @@ from __future__ import annotations +from enum import Enum +from typing import Any + from pydantic import BaseModel, Field +class ConfigurationStatus(str, Enum): + """Configuration version status enumeration.""" + + ARCHIVED = "archived" + ERRORED = "errored" + FETCHING = "fetching" + PENDING = "pending" + UPLOADED = "uploaded" + + +class ConfigurationSource(str, Enum): + """Configuration version source enumeration.""" + + API = "tfe-api" + BITBUCKET = "bitbucket" + GITHUB = "github" + GITLAB = "gitlab" + ADO = "ado" + TERRAFORM = "terraform" + + +class ConfigVerIncludeOpt(str, Enum): + """Configuration version include options.""" + + INGRESS_ATTRIBUTES = "ingress_attributes" + + +class IngressAttributes(BaseModel): + """Ingress attributes model.""" + + branch: str | None = None + clone_url: str | None = Field(alias="clone-url", default=None) + commit_message: str | None = Field(alias="commit-message", default=None) + commit_sha: str | None = Field(alias="commit-sha", default=None) + commit_url: str | None = Field(alias="commit-url", default=None) + compare_url: str | None = Field(alias="compare-url", default=None) + identifier: str | None = None + is_pull_request: bool | None = Field(alias="is-pull-request", default=None) + on_default_branch: bool | None = Field(alias="on-default-branch", default=None) + pull_request_number: int | None = Field(alias="pull-request-number", default=None) + pull_request_url: str | None = Field(alias="pull-request-url", default=None) + pull_request_title: str | None = Field(alias="pull-request-title", default=None) + pull_request_body: str | None = Field(alias="pull-request-body", default=None) + tag: str | None = None + sender_username: str | None = Field(alias="sender-username", default=None) + sender_avatar_url: str | None = Field(alias="sender-avatar-url", default=None) + sender_html_url: str | None = Field(alias="sender-html-url", default=None) + + model_config = {"populate_by_name": True} + + class ConfigurationVersion(BaseModel): + """Configuration version model.""" + id: str - auto_queue_runs: bool = Field(..., alias="auto-queue-runs") - error: str | None = Field(None, alias="error") - error_message: str | None = Field(None, alias="error-message") - # source: ConfigurationSource = Field(..., alias="source") - speculative: bool = Field(..., alias="speculative") - provisional: bool = Field(..., alias="provisional") - # status: ConfigurationStatus = Field(..., alias="status") - # status_timestamps: CVStatusTimestamps = Field(..., alias="status-timestamps") - upload_url: str | None = Field(None, alias="upload-url") - # ingress_attributes: IngressAttributes | None = Field(None, alias="ingress-attributes") + auto_queue_runs: bool = Field(alias="auto-queue-runs", default=False) + error: str | None = None + error_message: str | None = Field(alias="error-message", default=None) + source: ConfigurationSource | None = None + speculative: bool = False + status: ConfigurationStatus | None = None + status_timestamps: dict[str, str] | None = Field( + alias="status-timestamps", default=None + ) + provisional: bool = False + upload_url: str | None = Field(alias="upload-url", default=None) + + # Relations + ingress_attributes: IngressAttributes | None = Field( + alias="ingress-attributes", default=None + ) + + # Links + links: dict[str, Any] | None = None + + model_config = {"populate_by_name": True} + + +class ConfigurationVersionList(BaseModel): + """Configuration version list response.""" + + items: list[ConfigurationVersion] + pagination: dict[str, Any] | None = None + + +class ConfigurationVersionListOptions(BaseModel): + """Options for listing configuration versions.""" + + # Pagination options + page_number: int | None = Field(alias="page[number]", default=None) + page_size: int | None = Field(alias="page[size]", default=None) + + # Include related resources + include: list[ConfigVerIncludeOpt] | None = None + + model_config = {"populate_by_name": True} + + +class ConfigurationVersionCreateOptions(BaseModel): + """Options for creating a configuration version.""" + + # Optional: When true, runs are queued automatically when the configuration version is uploaded + auto_queue_runs: bool | None = Field(alias="auto-queue-runs", default=None) + + # Optional: When true, this configuration version can only be used for planning + speculative: bool | None = None + + # Optional: When true, this configuration version is provisional + provisional: bool | None = None + + model_config = {"populate_by_name": True} + + +class ConfigurationVersionReadOptions(BaseModel): + """Options for reading a configuration version.""" + + # Include related resources + include: list[ConfigVerIncludeOpt] | None = None + + +# Upload-related classes +class ConfigurationVersionUpload(BaseModel): + """Configuration version upload response.""" + + upload_url: str = Field(alias="upload-url") + + model_config = {"populate_by_name": True} diff --git a/src/pytfe/models/configuration_version_types.py b/src/pytfe/models/configuration_version_types.py deleted file mode 100644 index 4355dc74..00000000 --- a/src/pytfe/models/configuration_version_types.py +++ /dev/null @@ -1,135 +0,0 @@ -from __future__ import annotations - -from enum import Enum -from typing import Any - -from pydantic import BaseModel, Field - - -class ConfigurationStatus(str, Enum): - """Configuration version status enumeration.""" - - ARCHIVED = "archived" - ERRORED = "errored" - FETCHING = "fetching" - PENDING = "pending" - UPLOADED = "uploaded" - - -class ConfigurationSource(str, Enum): - """Configuration version source enumeration.""" - - API = "tfe-api" - BITBUCKET = "bitbucket" - GITHUB = "github" - GITLAB = "gitlab" - ADO = "ado" - TERRAFORM = "terraform" - - -class ConfigVerIncludeOpt(str, Enum): - """Configuration version include options.""" - - INGRESS_ATTRIBUTES = "ingress_attributes" - - -class IngressAttributes(BaseModel): - """Ingress attributes model.""" - - branch: str | None = None - clone_url: str | None = Field(alias="clone-url", default=None) - commit_message: str | None = Field(alias="commit-message", default=None) - commit_sha: str | None = Field(alias="commit-sha", default=None) - commit_url: str | None = Field(alias="commit-url", default=None) - compare_url: str | None = Field(alias="compare-url", default=None) - identifier: str | None = None - is_pull_request: bool | None = Field(alias="is-pull-request", default=None) - on_default_branch: bool | None = Field(alias="on-default-branch", default=None) - pull_request_number: int | None = Field(alias="pull-request-number", default=None) - pull_request_url: str | None = Field(alias="pull-request-url", default=None) - pull_request_title: str | None = Field(alias="pull-request-title", default=None) - pull_request_body: str | None = Field(alias="pull-request-body", default=None) - tag: str | None = None - sender_username: str | None = Field(alias="sender-username", default=None) - sender_avatar_url: str | None = Field(alias="sender-avatar-url", default=None) - sender_html_url: str | None = Field(alias="sender-html-url", default=None) - - model_config = {"populate_by_name": True} - - -class ConfigurationVersion(BaseModel): - """Configuration version model.""" - - id: str - auto_queue_runs: bool = Field(alias="auto-queue-runs") - error: str | None = None - error_message: str | None = Field(alias="error-message", default=None) - source: ConfigurationSource - speculative: bool = False - status: ConfigurationStatus - status_timestamps: dict[str, str] | None = Field( - alias="status-timestamps", default=None - ) - provisional: bool = False - upload_url: str | None = Field(alias="upload-url", default=None) - - # Relations - ingress_attributes: IngressAttributes | None = Field( - alias="ingress-attributes", default=None - ) - - # Links - links: dict[str, Any] | None = None - - model_config = {"populate_by_name": True} - - -class ConfigurationVersionList(BaseModel): - """Configuration version list response.""" - - items: list[ConfigurationVersion] - pagination: dict[str, Any] | None = None - - -class ConfigurationVersionListOptions(BaseModel): - """Options for listing configuration versions.""" - - # Pagination options - page_number: int | None = Field(alias="page[number]", default=None) - page_size: int | None = Field(alias="page[size]", default=None) - - # Include related resources - include: list[ConfigVerIncludeOpt] | None = None - - model_config = {"populate_by_name": True} - - -class ConfigurationVersionCreateOptions(BaseModel): - """Options for creating a configuration version.""" - - # Optional: When true, runs are queued automatically when the configuration version is uploaded - auto_queue_runs: bool | None = Field(alias="auto-queue-runs", default=None) - - # Optional: When true, this configuration version can only be used for planning - speculative: bool | None = None - - # Optional: When true, this configuration version is provisional - provisional: bool | None = None - - model_config = {"populate_by_name": True} - - -class ConfigurationVersionReadOptions(BaseModel): - """Options for reading a configuration version.""" - - # Include related resources - include: list[ConfigVerIncludeOpt] | None = None - - -# Upload-related classes -class ConfigurationVersionUpload(BaseModel): - """Configuration version upload response.""" - - upload_url: str = Field(alias="upload-url") - - model_config = {"populate_by_name": True} diff --git a/src/pytfe/models/registry_module_types.py b/src/pytfe/models/registry_module.py similarity index 94% rename from src/pytfe/models/registry_module_types.py rename to src/pytfe/models/registry_module.py index 3d20a88d..71f911f2 100644 --- a/src/pytfe/models/registry_module_types.py +++ b/src/pytfe/models/registry_module.py @@ -7,15 +7,6 @@ from pydantic import BaseModel, ConfigDict, Field -# Load the main types.py file to get Organization and other main types -# Path: from /src/tfe/types/registry_module_types.py to /src/tfe/types.py -types_py_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "types.py") -spec = importlib.util.spec_from_file_location("main_types", types_py_path) -if spec is not None and spec.loader is not None: - main_types = importlib.util.module_from_spec(spec) - spec.loader.exec_module(main_types) -else: - raise ImportError("Could not load main types module") class RegistryName(str, Enum): diff --git a/src/pytfe/models/registry_provider_types.py b/src/pytfe/models/registry_provider.py similarity index 100% rename from src/pytfe/models/registry_provider_types.py rename to src/pytfe/models/registry_provider.py diff --git a/src/pytfe/models/workspace.py b/src/pytfe/models/workspace.py index 19e3aea6..759cfec4 100644 --- a/src/pytfe/models/workspace.py +++ b/src/pytfe/models/workspace.py @@ -248,11 +248,6 @@ class WorkspaceList(BaseModel): pagination: Pagination | None = None -class TagList(BaseModel): - items: list[Tag] = Field(default_factory=list) - pagination: Pagination | None = None - - class WorkspaceRemoveVCSConnectionOptions(BaseModel): """Options for removing VCS connection from a workspace.""" diff --git a/src/pytfe/resources/configuration_version.py b/src/pytfe/resources/configuration_version.py index 8fdb33be..50c51251 100644 --- a/src/pytfe/resources/configuration_version.py +++ b/src/pytfe/resources/configuration_version.py @@ -12,7 +12,7 @@ ServerError, TFEError, ) -from ..models.configuration_version_types import ( +from ..models.configuration_version import ( ConfigurationVersion, ConfigurationVersionCreateOptions, ConfigurationVersionListOptions, diff --git a/src/pytfe/resources/registry_module.py b/src/pytfe/resources/registry_module.py index 1991895b..8daf49e9 100644 --- a/src/pytfe/resources/registry_module.py +++ b/src/pytfe/resources/registry_module.py @@ -9,7 +9,7 @@ ERR_INVALID_ORG, ERR_INVALID_VERSION, ) -from ..models.registry_module_types import ( +from ..models.registry_module import ( AgentExecutionMode, Commit, CommitList, diff --git a/src/pytfe/resources/registry_provider.py b/src/pytfe/resources/registry_provider.py index 3c1fd228..2511636d 100644 --- a/src/pytfe/resources/registry_provider.py +++ b/src/pytfe/resources/registry_provider.py @@ -6,7 +6,7 @@ from ..errors import ( ERR_INVALID_ORG, ) -from ..models.registry_provider_types import ( +from ..models.registry_provider import ( RegistryName, RegistryProvider, RegistryProviderCreateOptions, diff --git a/tests/units/test_apply.py b/tests/units/test_apply.py index 92da1903..458c87bd 100644 --- a/tests/units/test_apply.py +++ b/tests/units/test_apply.py @@ -65,7 +65,7 @@ def test_read_apply_success(self): assert result.resource_destructions == 0 assert result.resource_imports == 0 - @patch("tfe.resources.apply.Applies.read") + @patch("pytfe.resources.apply.Applies.read") def test_logs_success(self, mock_read): """Test successful logs retrieval.""" # Mock the apply object @@ -81,7 +81,7 @@ def test_logs_success(self, mock_read): # Verify it returns empty string (placeholder implementation) assert result == "" - @patch("tfe.resources.apply.Applies.read") + @patch("pytfe.resources.apply.Applies.read") def test_logs_no_url_error(self, mock_read): """Test logs method when apply has no log URL.""" # Mock apply with no log URL diff --git a/tests/units/test_configuration_version.py b/tests/units/test_configuration_version.py index 240fc162..b1ac5b9c 100644 --- a/tests/units/test_configuration_version.py +++ b/tests/units/test_configuration_version.py @@ -22,7 +22,7 @@ import pytest from src.pytfe.errors import NotFound, TFEError -from src.pytfe.models.configuration_version_types import ( +from pytfe.models.configuration_version import ( ConfigurationSource, ConfigurationStatus, ConfigurationVersionCreateOptions, @@ -348,11 +348,11 @@ def test_upload_missing_slug(self, configuration_versions_service): upload_url = "https://example.com/upload" directory_path = "/tmp/test" - with patch("src.tfe.utils.slug", None): + with patch("src.pytfe.utils.slug", None): with pytest.raises(ImportError, match="go-slug package is required"): configuration_versions_service.upload(upload_url, directory_path) - @patch("src.tfe.utils.slug") + @patch("src.pytfe.utils.slug") def test_upload_success(self, mock_slug, configuration_versions_service): """Test successful upload.""" # Mock slug.pack diff --git a/tests/units/test_project.py b/tests/units/test_project.py index d04e1e76..fd22f1b8 100644 --- a/tests/units/test_project.py +++ b/tests/units/test_project.py @@ -1,14 +1,16 @@ from unittest.mock import Mock from pytfe.resources.projects import Projects, _safe_str -from pytfe.types import ( - EffectiveTagBinding, +from pytfe.models.project import ( Project, ProjectAddTagBindingsOptions, ProjectCreateOptions, ProjectUpdateOptions, TagBinding, ) +from pytfe.models import ( + EffectiveTagBinding, +) class TestProjects: diff --git a/tests/units/test_query_run.py b/tests/units/test_query_run.py index ae221152..8808090c 100644 --- a/tests/units/test_query_run.py +++ b/tests/units/test_query_run.py @@ -450,7 +450,7 @@ def client(self): from unittest.mock import MagicMock, patch # Mock the HTTPTransport to prevent any network calls during initialization - with patch("tfe.client.HTTPTransport") as mock_transport_class: + with patch("pytfe.client.HTTPTransport") as mock_transport_class: mock_transport_instance = MagicMock() mock_transport_class.return_value = mock_transport_instance diff --git a/tests/units/test_run_trigger.py b/tests/units/test_run_trigger.py index 75f96ec0..901dafb8 100644 --- a/tests/units/test_run_trigger.py +++ b/tests/units/test_run_trigger.py @@ -310,7 +310,7 @@ def test_validate_run_trigger_filter_param_validations(self, run_triggers_servic """Test validation with invalid filter parameter.""" # This should be tested by mocking the enum validation - with patch("tfe.resources.run_trigger.RunTriggerFilterOp") as mock_enum: + with patch("pytfe.resources.run_trigger.RunTriggerFilterOp") as mock_enum: mock_enum.__contains__ = Mock(return_value=False) with pytest.raises(InvalidRunTriggerTypeError): diff --git a/tests/units/test_variable_sets.py b/tests/units/test_variable_sets.py index 514757f7..d7a0ca99 100644 --- a/tests/units/test_variable_sets.py +++ b/tests/units/test_variable_sets.py @@ -5,7 +5,7 @@ import pytest from pytfe.resources.variable_sets import VariableSets, VariableSetVariables -from pytfe.types import ( +from pytfe.models.variable_set import ( CategoryType, Parent, Project, diff --git a/tests/units/test_workspaces.py b/tests/units/test_workspaces.py index e3186c19..b77154e6 100644 --- a/tests/units/test_workspaces.py +++ b/tests/units/test_workspaces.py @@ -37,14 +37,8 @@ ) from src.pytfe.models.workspace import ( - DataRetentionPolicyDeleteOlderSetOptions, - DataRetentionPolicyDontDeleteSetOptions, - DataRetentionPolicySetOptions, - EffectiveTagBinding, ExecutionMode, Project, - Tag, - TagBinding, VCSRepo, Workspace, WorkspaceAddRemoteStateConsumersOptions, From d333dc3d5e9c3c3a4051354840853444652279d6 Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Fri, 10 Oct 2025 10:04:58 +0530 Subject: [PATCH 7/9] changes to models and cleanup --- examples/org.py | 3 +- examples/project.py | 8 +-- examples/variable_sets.py | 3 +- examples/variables.py | 3 +- examples/workspace.py | 3 +- src/pytfe/models/__init__.py | 81 +++++++++++------------ src/pytfe/models/common.py | 3 +- src/pytfe/models/data_retention_policy.py | 63 ++++++++++++++++++ src/pytfe/models/organization.py | 61 +---------------- src/pytfe/models/project.py | 4 -- src/pytfe/models/registry_module.py | 3 - src/pytfe/models/variable.py | 2 +- src/pytfe/models/variable_set.py | 5 +- src/pytfe/models/workspace.py | 4 +- src/pytfe/resources/_base.py | 2 +- src/pytfe/resources/admin/settings.py | 1 - src/pytfe/resources/organizations.py | 6 +- src/pytfe/resources/projects.py | 9 ++- src/pytfe/resources/variable_sets.py | 2 +- src/pytfe/resources/workspaces.py | 16 ++--- tests/units/test_configuration_version.py | 2 +- tests/units/test_project.py | 8 +-- tests/units/test_variable_sets.py | 2 +- tests/units/test_workspaces.py | 22 +++--- 24 files changed, 153 insertions(+), 163 deletions(-) create mode 100644 src/pytfe/models/data_retention_policy.py diff --git a/examples/org.py b/examples/org.py index 82475de4..56e51e56 100644 --- a/examples/org.py +++ b/examples/org.py @@ -1,4 +1,3 @@ -from pytfe import TFEClient, TFEConfig from pytfe.types import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, @@ -6,6 +5,8 @@ ReadRunQueueOptions, ) +from pytfe import TFEClient, TFEConfig + def test_basic_org_operations(client): """Test basic organization CRUD operations.""" diff --git a/examples/project.py b/examples/project.py index 2d335848..d7fe9d76 100644 --- a/examples/project.py +++ b/examples/project.py @@ -28,10 +28,6 @@ import uuid import pytest - -from pytfe._http import HTTPTransport -from pytfe.config import TFEConfig -from pytfe.resources.projects import Projects from pytfe.types import ( ProjectAddTagBindingsOptions, ProjectCreateOptions, @@ -40,6 +36,10 @@ TagBinding, ) +from pytfe._http import HTTPTransport +from pytfe.config import TFEConfig +from pytfe.resources.projects import Projects + @pytest.fixture def integration_client(): diff --git a/examples/variable_sets.py b/examples/variable_sets.py index 9ca22116..85749f91 100644 --- a/examples/variable_sets.py +++ b/examples/variable_sets.py @@ -15,7 +15,6 @@ import os -from pytfe import TFEClient, TFEConfig from pytfe.types import ( CategoryType, Parent, @@ -34,6 +33,8 @@ Workspace, ) +from pytfe import TFEClient, TFEConfig + def variable_set_example(): """Demonstrate Variable Set operations.""" diff --git a/examples/variables.py b/examples/variables.py index d5f4b0c2..c0d62253 100644 --- a/examples/variables.py +++ b/examples/variables.py @@ -11,9 +11,10 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from pytfe import TFEClient, TFEConfig from pytfe.types import CategoryType, VariableCreateOptions, VariableUpdateOptions +from pytfe import TFEClient, TFEConfig + def main(): """Test all variable operations in a workspace.""" diff --git a/examples/workspace.py b/examples/workspace.py index 13fddbbf..c0ae26f5 100644 --- a/examples/workspace.py +++ b/examples/workspace.py @@ -22,7 +22,6 @@ # Add the source directory to the path for direct execution sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from pytfe import TFEClient, TFEConfig from pytfe.types import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, @@ -47,6 +46,8 @@ WorkspaceUpdateRemoteStateConsumersOptions, ) +from pytfe import TFEClient, TFEConfig + class WorkspaceManager: """Comprehensive workspace management utility.""" diff --git a/src/pytfe/models/__init__.py b/src/pytfe/models/__init__.py index 94abf509..cc79a7f9 100644 --- a/src/pytfe/models/__init__.py +++ b/src/pytfe/models/__init__.py @@ -19,6 +19,17 @@ AgentTokenListOptions, ) +# ── Core models split out of old types.py ───────────────────────────────────── +# Adjust these imports to match where you placed them during the split. +# Common / pagination / enums +from .common import ( + EffectiveTagBinding, + Pagination, + Tag, + TagBinding, + TagList, +) # if you put ExecutionMode enum here + # ── Configuration Versions ──────────────────────────────────────────────────── # (Old: .configuration_version_types) → import directly from real module from .configuration_version import ( @@ -34,6 +45,17 @@ IngressAttributes, ) +# Data retention policy family +from .data_retention_policy import ( + DataRetentionPolicy, + DataRetentionPolicyChoice, + DataRetentionPolicyDeleteOlder, + DataRetentionPolicyDeleteOlderSetOptions, + DataRetentionPolicyDontDelete, + DataRetentionPolicyDontDeleteSetOptions, + DataRetentionPolicySetOptions, +) + # ── OAuth ───────────────────────────────────────────────────────────────────── from .oauth_client import ( OAuthClient, @@ -47,7 +69,6 @@ OAuthClientUpdateOptions, ServiceProviderType, ) - from .oauth_token import ( OAuthToken, OAuthTokenList, @@ -55,6 +76,18 @@ OAuthTokenUpdateOptions, ) +# Organization / Project +from .organization import ( + Entitlements, + ExecutionMode, + Organization, + OrganizationCreateOptions, + OrganizationUpdateOptions, + ReadRunQueueOptions, + RunQueue, +) +from .project import Project + # ── Query Runs ──────────────────────────────────────────────────────────────── from .query_run import ( QueryRun, @@ -103,7 +136,6 @@ TerraformRegistryModule, TestConfig, ) - from .registry_provider import ( RegistryProvider, RegistryProviderCreateOptions, @@ -124,6 +156,12 @@ ReservedTagKeyUpdateOptions, ) +# Runs +from .run import ( + Run, + RunStatus, +) + # ── SSH Keys ────────────────────────────────────────────────────────────────── from .ssh_key import ( SSHKey, @@ -133,34 +171,6 @@ SSHKeyUpdateOptions, ) -# ── Core models split out of old types.py ───────────────────────────────────── -# Adjust these imports to match where you placed them during the split. - -# Common / pagination / enums -from .common import Pagination, Tag, TagBinding, EffectiveTagBinding, TagList # if you put ExecutionMode enum here -from .organization import Entitlements, ExecutionMode - -# Organization / Project -from .organization import ( - Organization, - OrganizationCreateOptions, - OrganizationUpdateOptions, -) - -from .project import Project - -# Data retention policy family -from .organization import ( - DataRetentionPolicy, - DataRetentionPolicyChoice, - DataRetentionPolicyDeleteOlder, - DataRetentionPolicyDeleteOlderSetOptions, - DataRetentionPolicyDontDelete, - DataRetentionPolicyDontDeleteSetOptions, - DataRetentionPolicySetOptions, -) - - # Variables from .variable import ( Variable, @@ -198,17 +208,6 @@ WorkspaceUpdateRemoteStateConsumersOptions, ) -# Runs -from .run import ( - Run, - RunStatus, -) - -from .organization import ( - RunQueue, - ReadRunQueueOptions, -) - # ── Public surface ──────────────────────────────────────────────────────────── __all__ = [ # OAuth diff --git a/src/pytfe/models/common.py b/src/pytfe/models/common.py index 829ee312..a6dd5698 100644 --- a/src/pytfe/models/common.py +++ b/src/pytfe/models/common.py @@ -1,7 +1,5 @@ from __future__ import annotations -from datetime import datetime -from enum import Enum from typing import Any from pydantic import BaseModel, Field @@ -26,6 +24,7 @@ class TagBinding(BaseModel): key: str value: str | None = None + class TagList(BaseModel): items: list[Tag] = Field(default_factory=list) pagination: Pagination | None = None diff --git a/src/pytfe/models/data_retention_policy.py b/src/pytfe/models/data_retention_policy.py new file mode 100644 index 00000000..6b6349ff --- /dev/null +++ b/src/pytfe/models/data_retention_policy.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from pydantic import BaseModel + + +class DataRetentionPolicy(BaseModel): + """Deprecated: Use DataRetentionPolicyDeleteOlder instead.""" + + id: str + delete_older_than_n_days: int + + +class DataRetentionPolicyDeleteOlder(BaseModel): + id: str + delete_older_than_n_days: int + + +class DataRetentionPolicyDontDelete(BaseModel): + id: str + + +class DataRetentionPolicyChoice(BaseModel): + """Polymorphic data retention policy choice.""" + + data_retention_policy: DataRetentionPolicy | None = None + data_retention_policy_delete_older: DataRetentionPolicyDeleteOlder | None = None + data_retention_policy_dont_delete: DataRetentionPolicyDontDelete | None = None + + def is_populated(self) -> bool: + """Returns whether one of the choices is populated.""" + return ( + self.data_retention_policy is not None + or self.data_retention_policy_delete_older is not None + or self.data_retention_policy_dont_delete is not None + ) + + def convert_to_legacy_struct(self) -> DataRetentionPolicy | None: + """Convert the DataRetentionPolicyChoice to the legacy DataRetentionPolicy struct.""" + if not self.is_populated(): + return None + + if self.data_retention_policy is not None: + return self.data_retention_policy + elif self.data_retention_policy_delete_older is not None: + return DataRetentionPolicy( + id=self.data_retention_policy_delete_older.id, + delete_older_than_n_days=self.data_retention_policy_delete_older.delete_older_than_n_days, + ) + return None + + +class DataRetentionPolicySetOptions(BaseModel): + """Deprecated: Use DataRetentionPolicyDeleteOlderSetOptions instead.""" + + delete_older_than_n_days: int + + +class DataRetentionPolicyDeleteOlderSetOptions(BaseModel): + delete_older_than_n_days: int + + +class DataRetentionPolicyDontDeleteSetOptions(BaseModel): + pass # No additional fields needed diff --git a/src/pytfe/models/organization.py b/src/pytfe/models/organization.py index 06d544d6..21e40b23 100644 --- a/src/pytfe/models/organization.py +++ b/src/pytfe/models/organization.py @@ -138,6 +138,7 @@ class Pagination(BaseModel): # Add other pagination fields as needed +# RunQueue represents the current run queue of an organization. class RunQueue(BaseModel): pagination: Pagination | None = None items: list[Run] = Field(default_factory=list) @@ -147,63 +148,3 @@ class ReadRunQueueOptions(BaseModel): # List options for pagination page_number: int | None = None page_size: int | None = None - - -class DataRetentionPolicy(BaseModel): - """Deprecated: Use DataRetentionPolicyDeleteOlder instead.""" - - id: str - delete_older_than_n_days: int - - -class DataRetentionPolicyDeleteOlder(BaseModel): - id: str - delete_older_than_n_days: int - - -class DataRetentionPolicyDontDelete(BaseModel): - id: str - - -class DataRetentionPolicyChoice(BaseModel): - """Polymorphic data retention policy choice.""" - - data_retention_policy: DataRetentionPolicy | None = None - data_retention_policy_delete_older: DataRetentionPolicyDeleteOlder | None = None - data_retention_policy_dont_delete: DataRetentionPolicyDontDelete | None = None - - def is_populated(self) -> bool: - """Returns whether one of the choices is populated.""" - return ( - self.data_retention_policy is not None - or self.data_retention_policy_delete_older is not None - or self.data_retention_policy_dont_delete is not None - ) - - def convert_to_legacy_struct(self) -> DataRetentionPolicy | None: - """Convert the DataRetentionPolicyChoice to the legacy DataRetentionPolicy struct.""" - if not self.is_populated(): - return None - - if self.data_retention_policy is not None: - return self.data_retention_policy - elif self.data_retention_policy_delete_older is not None: - return DataRetentionPolicy( - id=self.data_retention_policy_delete_older.id, - delete_older_than_n_days=self.data_retention_policy_delete_older.delete_older_than_n_days, - ) - return None - - -class DataRetentionPolicySetOptions(BaseModel): - """Deprecated: Use DataRetentionPolicyDeleteOlderSetOptions instead.""" - - delete_older_than_n_days: int - - -class DataRetentionPolicyDeleteOlderSetOptions(BaseModel): - delete_older_than_n_days: int - - -class DataRetentionPolicyDontDeleteSetOptions(BaseModel): - pass # No additional fields needed diff --git a/src/pytfe/models/project.py b/src/pytfe/models/project.py index 405a77f6..3f4b9c6c 100644 --- a/src/pytfe/models/project.py +++ b/src/pytfe/models/project.py @@ -1,9 +1,5 @@ from __future__ import annotations -from datetime import datetime -from enum import Enum -from typing import Any - from pydantic import BaseModel, Field from .common import TagBinding diff --git a/src/pytfe/models/registry_module.py b/src/pytfe/models/registry_module.py index 71f911f2..d7c39be6 100644 --- a/src/pytfe/models/registry_module.py +++ b/src/pytfe/models/registry_module.py @@ -1,14 +1,11 @@ from __future__ import annotations -import importlib.util -import os from enum import Enum from typing import Any from pydantic import BaseModel, ConfigDict, Field - class RegistryName(str, Enum): """Registry name enum for public/private registries.""" diff --git a/src/pytfe/models/variable.py b/src/pytfe/models/variable.py index a50660ec..e9a137a5 100644 --- a/src/pytfe/models/variable.py +++ b/src/pytfe/models/variable.py @@ -2,7 +2,7 @@ from enum import Enum -from pydantic import BaseModel, Field +from pydantic import BaseModel class CategoryType(str, Enum): diff --git a/src/pytfe/models/variable_set.py b/src/pytfe/models/variable_set.py index 9bd9b976..0daee068 100644 --- a/src/pytfe/models/variable_set.py +++ b/src/pytfe/models/variable_set.py @@ -6,10 +6,9 @@ from pydantic import BaseModel, Field from .organization import Organization -from .workspace import Workspace -from .variable import CategoryType - from .project import Project +from .variable import CategoryType +from .workspace import Workspace class VariableSetIncludeOpt(str, Enum): diff --git a/src/pytfe/models/workspace.py b/src/pytfe/models/workspace.py index 759cfec4..22056215 100644 --- a/src/pytfe/models/workspace.py +++ b/src/pytfe/models/workspace.py @@ -6,10 +6,10 @@ from pydantic import BaseModel, Field +from .common import EffectiveTagBinding, Pagination, Tag, TagBinding +from .data_retention_policy import DataRetentionPolicy, DataRetentionPolicyChoice from .organization import ExecutionMode from .project import Project -from .common import Pagination, Tag, TagBinding, EffectiveTagBinding -from .organization import DataRetentionPolicy, DataRetentionPolicyChoice class Workspace(BaseModel): diff --git a/src/pytfe/resources/_base.py b/src/pytfe/resources/_base.py index 95ecaed8..0d8bf103 100644 --- a/src/pytfe/resources/_base.py +++ b/src/pytfe/resources/_base.py @@ -1,6 +1,6 @@ from __future__ import annotations -from collections.abc import AsyncIterator, Iterator +from collections.abc import Iterator from typing import Any from .._http import HTTPTransport diff --git a/src/pytfe/resources/admin/settings.py b/src/pytfe/resources/admin/settings.py index f2b7b556..8ee94076 100644 --- a/src/pytfe/resources/admin/settings.py +++ b/src/pytfe/resources/admin/settings.py @@ -9,4 +9,3 @@ class AdminSettings(_Service): def terraform_versions(self) -> Any: r = self.t.request("GET", "/api/v2/admin/terraform-versions") return r.json() - diff --git a/src/pytfe/resources/organizations.py b/src/pytfe/resources/organizations.py index 78977fee..fe6f94c5 100644 --- a/src/pytfe/resources/organizations.py +++ b/src/pytfe/resources/organizations.py @@ -9,8 +9,7 @@ ERR_REQUIRED_EMAIL, ERR_REQUIRED_NAME, ) -from ..models.organization import ( - Capacity, +from ..models.data_retention_policy import ( DataRetentionPolicy, DataRetentionPolicyChoice, DataRetentionPolicyDeleteOlder, @@ -18,6 +17,9 @@ DataRetentionPolicyDontDelete, DataRetentionPolicyDontDeleteSetOptions, DataRetentionPolicySetOptions, +) +from ..models.organization import ( + Capacity, Entitlements, Organization, OrganizationCreateOptions, diff --git a/src/pytfe/resources/projects.py b/src/pytfe/resources/projects.py index 3cf9fcf7..9011a3cb 100644 --- a/src/pytfe/resources/projects.py +++ b/src/pytfe/resources/projects.py @@ -5,6 +5,10 @@ from collections.abc import Iterator from typing import Any +from ..models.common import ( + EffectiveTagBinding, + TagBinding, +) from ..models.project import ( Project, ProjectAddTagBindingsOptions, @@ -12,11 +16,6 @@ ProjectListOptions, ProjectUpdateOptions, ) - -from ..models.common import ( - EffectiveTagBinding, - TagBinding, -) from ..utils import valid_string, valid_string_id from ._base import _Service diff --git a/src/pytfe/resources/variable_sets.py b/src/pytfe/resources/variable_sets.py index 357ccd14..4bf4353c 100644 --- a/src/pytfe/resources/variable_sets.py +++ b/src/pytfe/resources/variable_sets.py @@ -4,7 +4,6 @@ from typing import Any from .._http import HTTPTransport -from ._base import _Service from ..models.variable_set import ( VariableSet, VariableSetApplyToProjectsOptions, @@ -22,6 +21,7 @@ VariableSetVariableListOptions, VariableSetVariableUpdateOptions, ) +from ._base import _Service class VariableSets(_Service): diff --git a/src/pytfe/resources/workspaces.py b/src/pytfe/resources/workspaces.py index 75215dfc..2a3a6b2f 100644 --- a/src/pytfe/resources/workspaces.py +++ b/src/pytfe/resources/workspaces.py @@ -16,8 +16,12 @@ WorkspaceMinimumLimitError, WorkspaceRequiredError, ) - -from ..models.organization import ( +from ..models.common import ( + EffectiveTagBinding, + Tag, + TagBinding, +) +from ..models.data_retention_policy import ( DataRetentionPolicy, DataRetentionPolicyChoice, DataRetentionPolicyDeleteOlder, @@ -26,13 +30,6 @@ DataRetentionPolicyDontDeleteSetOptions, DataRetentionPolicySetOptions, ) - -from ..models.common import ( - Tag, - TagBinding, - EffectiveTagBinding, -) - from ..models.workspace import ( ExecutionMode, LockedByChoice, @@ -59,7 +56,6 @@ WorkspaceUpdateOptions, WorkspaceUpdateRemoteStateConsumersOptions, ) - from ..utils import ( valid_string, valid_string_id, diff --git a/tests/units/test_configuration_version.py b/tests/units/test_configuration_version.py index b1ac5b9c..f4f0fa20 100644 --- a/tests/units/test_configuration_version.py +++ b/tests/units/test_configuration_version.py @@ -21,7 +21,6 @@ import pytest -from src.pytfe.errors import NotFound, TFEError from pytfe.models.configuration_version import ( ConfigurationSource, ConfigurationStatus, @@ -30,6 +29,7 @@ ConfigurationVersionReadOptions, ConfigVerIncludeOpt, ) +from src.pytfe.errors import NotFound, TFEError from src.pytfe.resources.configuration_version import ConfigurationVersions diff --git a/tests/units/test_project.py b/tests/units/test_project.py index fd22f1b8..262787af 100644 --- a/tests/units/test_project.py +++ b/tests/units/test_project.py @@ -1,6 +1,8 @@ from unittest.mock import Mock -from pytfe.resources.projects import Projects, _safe_str +from pytfe.models import ( + EffectiveTagBinding, +) from pytfe.models.project import ( Project, ProjectAddTagBindingsOptions, @@ -8,9 +10,7 @@ ProjectUpdateOptions, TagBinding, ) -from pytfe.models import ( - EffectiveTagBinding, -) +from pytfe.resources.projects import Projects, _safe_str class TestProjects: diff --git a/tests/units/test_variable_sets.py b/tests/units/test_variable_sets.py index d7a0ca99..01ad8689 100644 --- a/tests/units/test_variable_sets.py +++ b/tests/units/test_variable_sets.py @@ -4,7 +4,6 @@ import pytest -from pytfe.resources.variable_sets import VariableSets, VariableSetVariables from pytfe.models.variable_set import ( CategoryType, Parent, @@ -25,6 +24,7 @@ VariableSetVariableUpdateOptions, Workspace, ) +from pytfe.resources.variable_sets import VariableSets, VariableSetVariables class TestVariableSets: diff --git a/tests/units/test_workspaces.py b/tests/units/test_workspaces.py index b77154e6..9448e8d2 100644 --- a/tests/units/test_workspaces.py +++ b/tests/units/test_workspaces.py @@ -19,26 +19,21 @@ RequiredSSHKeyIDError, WorkspaceMinimumLimitError, ) -from src.pytfe.resources.workspaces import Workspaces, _ws_from - -from src.pytfe.models.organization import ( +from src.pytfe.models.common import ( + EffectiveTagBinding, + Tag, + TagBinding, +) +from src.pytfe.models.data_retention_policy import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, DataRetentionPolicySetOptions, +) +from src.pytfe.models.organization import ( ExecutionMode, ) - from src.pytfe.models.project import Project - -from src.pytfe.models.common import ( - EffectiveTagBinding, - Tag, - TagBinding, -) - from src.pytfe.models.workspace import ( - ExecutionMode, - Project, VCSRepo, Workspace, WorkspaceAddRemoteStateConsumersOptions, @@ -57,6 +52,7 @@ WorkspaceUpdateOptions, WorkspaceUpdateRemoteStateConsumersOptions, ) +from src.pytfe.resources.workspaces import Workspaces, _ws_from class TestWorkspaceOperations: From 2818df6d4fe86116e5d8b70576474c2320f43b58 Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Fri, 10 Oct 2025 11:20:53 +0530 Subject: [PATCH 8/9] update examples to point to new package name --- examples/org.py | 2 +- examples/policy.py | 2 +- examples/policy_check.py | 4 ++-- examples/project.py | 2 +- examples/run.py | 2 +- examples/run_events.py | 2 +- examples/run_task.py | 2 +- examples/run_trigger.py | 4 ++-- examples/state_versions.py | 4 ++-- examples/variables.py | 2 +- examples/workspace.py | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/org.py b/examples/org.py index 56e51e56..dad29bde 100644 --- a/examples/org.py +++ b/examples/org.py @@ -1,4 +1,4 @@ -from pytfe.types import ( +from pytfe.models import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, OrganizationCreateOptions, diff --git a/examples/policy.py b/examples/policy.py index 1b849be8..ff539e09 100644 --- a/examples/policy.py +++ b/examples/policy.py @@ -28,8 +28,8 @@ PolicyCreateOptions, PolicyListOptions, PolicyUpdateOptions, + PolicyKind ) -from pytfe.models.policy_set import PolicyKind def _print_header(title: str): diff --git a/examples/policy_check.py b/examples/policy_check.py index f47a1804..771a576f 100644 --- a/examples/policy_check.py +++ b/examples/policy_check.py @@ -3,8 +3,8 @@ import argparse import os -from tfe import TFEClient, TFEConfig -from tfe.models.policy_check import PolicyCheckListOptions +from pytfe import TFEClient, TFEConfig +from pytfe.models import PolicyCheckListOptions def _print_header(title: str): diff --git a/examples/project.py b/examples/project.py index d7fe9d76..aecc6620 100644 --- a/examples/project.py +++ b/examples/project.py @@ -28,7 +28,7 @@ import uuid import pytest -from pytfe.types import ( +from pytfe.models import ( ProjectAddTagBindingsOptions, ProjectCreateOptions, ProjectListOptions, diff --git a/examples/run.py b/examples/run.py index 02daab3d..c4760ac9 100644 --- a/examples/run.py +++ b/examples/run.py @@ -5,7 +5,7 @@ from datetime import datetime from pytfe import TFEClient, TFEConfig -from pytfe.models.run import ( +from pytfe.models import ( RunCreateOptions, RunIncludeOpt, RunListForOrganizationOptions, diff --git a/examples/run_events.py b/examples/run_events.py index df0f782c..75033c2c 100644 --- a/examples/run_events.py +++ b/examples/run_events.py @@ -38,7 +38,7 @@ import os from pytfe import TFEClient, TFEConfig -from pytfe.models.run_event import ( +from pytfe.models import ( RunEventIncludeOpt, RunEventListOptions, RunEventReadOptions, diff --git a/examples/run_task.py b/examples/run_task.py index 9b669754..5f27a5e0 100644 --- a/examples/run_task.py +++ b/examples/run_task.py @@ -2,7 +2,7 @@ import traceback from pytfe import TFEClient, TFEConfig -from pytfe.models.run_task import ( +from pytfe.models import ( RunTaskCreateOptions, RunTaskIncludeOptions, RunTaskListOptions, diff --git a/examples/run_trigger.py b/examples/run_trigger.py index 97023a95..ffe43933 100644 --- a/examples/run_trigger.py +++ b/examples/run_trigger.py @@ -2,13 +2,13 @@ import traceback from pytfe import TFEClient, TFEConfig -from pytfe.models.run_trigger import ( +from pytfe.models import ( RunTriggerCreateOptions, RunTriggerFilterOp, RunTriggerIncludeOp, RunTriggerListOptions, ) -from pytfe.models.workspace import Workspace +from pytfe.models import Workspace def run_trigger_list(client, workspace_id): diff --git a/examples/state_versions.py b/examples/state_versions.py index 03278bfc..6d19ff99 100644 --- a/examples/state_versions.py +++ b/examples/state_versions.py @@ -6,12 +6,12 @@ from pytfe import TFEClient, TFEConfig from pytfe.errors import ErrStateVersionUploadNotSupported -from pytfe.models.state_version import ( +from pytfe.models import ( StateVersionCreateOptions, StateVersionCurrentOptions, StateVersionListOptions, ) -from pytfe.models.state_version_output import StateVersionOutputsListOptions +from pytfe.models import StateVersionOutputsListOptions def _print_header(title: str): diff --git a/examples/variables.py b/examples/variables.py index c0d62253..c8962190 100644 --- a/examples/variables.py +++ b/examples/variables.py @@ -11,7 +11,7 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from pytfe.types import CategoryType, VariableCreateOptions, VariableUpdateOptions +from pytfe.models import CategoryType, VariableCreateOptions, VariableUpdateOptions from pytfe import TFEClient, TFEConfig diff --git a/examples/workspace.py b/examples/workspace.py index c0ae26f5..880d193a 100644 --- a/examples/workspace.py +++ b/examples/workspace.py @@ -22,7 +22,7 @@ # Add the source directory to the path for direct execution sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from pytfe.types import ( +from pytfe.models import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, ExecutionMode, From 38e07eb77686842eef9e6955f9e00ed8878d57f0 Mon Sep 17 00:00:00 2001 From: Prabuddha Chakraborty Date: Fri, 10 Oct 2025 11:29:39 +0530 Subject: [PATCH 9/9] fix lint fails --- examples/org.py | 3 +-- examples/policy.py | 2 +- examples/project.py | 6 +++--- examples/run_trigger.py | 2 +- examples/state_versions.py | 2 +- examples/variables.py | 3 +-- examples/workspace.py | 3 +-- 7 files changed, 9 insertions(+), 12 deletions(-) diff --git a/examples/org.py b/examples/org.py index dad29bde..93b8f0f7 100644 --- a/examples/org.py +++ b/examples/org.py @@ -1,3 +1,4 @@ +from pytfe import TFEClient, TFEConfig from pytfe.models import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, @@ -5,8 +6,6 @@ ReadRunQueueOptions, ) -from pytfe import TFEClient, TFEConfig - def test_basic_org_operations(client): """Test basic organization CRUD operations.""" diff --git a/examples/policy.py b/examples/policy.py index ff539e09..d1965974 100644 --- a/examples/policy.py +++ b/examples/policy.py @@ -26,9 +26,9 @@ from pytfe.models import ( EnforcementLevel, PolicyCreateOptions, + PolicyKind, PolicyListOptions, PolicyUpdateOptions, - PolicyKind ) diff --git a/examples/project.py b/examples/project.py index aecc6620..6999e4f6 100644 --- a/examples/project.py +++ b/examples/project.py @@ -28,6 +28,9 @@ import uuid import pytest + +from pytfe._http import HTTPTransport +from pytfe.config import TFEConfig from pytfe.models import ( ProjectAddTagBindingsOptions, ProjectCreateOptions, @@ -35,9 +38,6 @@ ProjectUpdateOptions, TagBinding, ) - -from pytfe._http import HTTPTransport -from pytfe.config import TFEConfig from pytfe.resources.projects import Projects diff --git a/examples/run_trigger.py b/examples/run_trigger.py index ffe43933..fb2a44fb 100644 --- a/examples/run_trigger.py +++ b/examples/run_trigger.py @@ -7,8 +7,8 @@ RunTriggerFilterOp, RunTriggerIncludeOp, RunTriggerListOptions, + Workspace, ) -from pytfe.models import Workspace def run_trigger_list(client, workspace_id): diff --git a/examples/state_versions.py b/examples/state_versions.py index 6d19ff99..6d3f8b82 100644 --- a/examples/state_versions.py +++ b/examples/state_versions.py @@ -10,8 +10,8 @@ StateVersionCreateOptions, StateVersionCurrentOptions, StateVersionListOptions, + StateVersionOutputsListOptions, ) -from pytfe.models import StateVersionOutputsListOptions def _print_header(title: str): diff --git a/examples/variables.py b/examples/variables.py index c8962190..bc5227eb 100644 --- a/examples/variables.py +++ b/examples/variables.py @@ -11,9 +11,8 @@ # Add the src directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from pytfe.models import CategoryType, VariableCreateOptions, VariableUpdateOptions - from pytfe import TFEClient, TFEConfig +from pytfe.models import CategoryType, VariableCreateOptions, VariableUpdateOptions def main(): diff --git a/examples/workspace.py b/examples/workspace.py index 880d193a..dcea019a 100644 --- a/examples/workspace.py +++ b/examples/workspace.py @@ -22,6 +22,7 @@ # Add the source directory to the path for direct execution sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) +from pytfe import TFEClient, TFEConfig from pytfe.models import ( DataRetentionPolicyDeleteOlderSetOptions, DataRetentionPolicyDontDeleteSetOptions, @@ -46,8 +47,6 @@ WorkspaceUpdateRemoteStateConsumersOptions, ) -from pytfe import TFEClient, TFEConfig - class WorkspaceManager: """Comprehensive workspace management utility."""