teams-phone-cli 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. teams_phone/__init__.py +3 -0
  2. teams_phone/__main__.py +7 -0
  3. teams_phone/cli/__init__.py +8 -0
  4. teams_phone/cli/api_check.py +267 -0
  5. teams_phone/cli/auth.py +201 -0
  6. teams_phone/cli/context.py +108 -0
  7. teams_phone/cli/helpers.py +65 -0
  8. teams_phone/cli/locations.py +308 -0
  9. teams_phone/cli/main.py +99 -0
  10. teams_phone/cli/numbers.py +1644 -0
  11. teams_phone/cli/policies.py +893 -0
  12. teams_phone/cli/tenants.py +364 -0
  13. teams_phone/cli/users.py +394 -0
  14. teams_phone/constants.py +97 -0
  15. teams_phone/exceptions.py +137 -0
  16. teams_phone/infrastructure/__init__.py +22 -0
  17. teams_phone/infrastructure/cache_manager.py +274 -0
  18. teams_phone/infrastructure/config_manager.py +209 -0
  19. teams_phone/infrastructure/debug_logger.py +321 -0
  20. teams_phone/infrastructure/graph_client.py +666 -0
  21. teams_phone/infrastructure/output_formatter.py +234 -0
  22. teams_phone/models/__init__.py +76 -0
  23. teams_phone/models/api_responses.py +69 -0
  24. teams_phone/models/auth.py +100 -0
  25. teams_phone/models/cache.py +25 -0
  26. teams_phone/models/config.py +66 -0
  27. teams_phone/models/location.py +36 -0
  28. teams_phone/models/number.py +184 -0
  29. teams_phone/models/policy.py +26 -0
  30. teams_phone/models/tenant.py +45 -0
  31. teams_phone/models/user.py +117 -0
  32. teams_phone/services/__init__.py +21 -0
  33. teams_phone/services/auth_service.py +536 -0
  34. teams_phone/services/bulk_operations.py +562 -0
  35. teams_phone/services/location_service.py +195 -0
  36. teams_phone/services/number_service.py +489 -0
  37. teams_phone/services/policy_service.py +330 -0
  38. teams_phone/services/tenant_service.py +205 -0
  39. teams_phone/services/user_service.py +435 -0
  40. teams_phone/utils.py +172 -0
  41. teams_phone_cli-0.1.2.dist-info/METADATA +15 -0
  42. teams_phone_cli-0.1.2.dist-info/RECORD +45 -0
  43. teams_phone_cli-0.1.2.dist-info/WHEEL +4 -0
  44. teams_phone_cli-0.1.2.dist-info/entry_points.txt +2 -0
  45. teams_phone_cli-0.1.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,184 @@
1
+ """Phone number assignment models for Teams Phone Graph API responses.
2
+
3
+ These models represent phone number inventory and async operation data from Microsoft Graph API.
4
+ All models use `extra="forbid"` for strict contract validation to detect API changes.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from enum import Enum
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field
11
+
12
+
13
+ class NumberType(str, Enum):
14
+ """Phone number type in Teams Phone.
15
+
16
+ Attributes:
17
+ DIRECT_ROUTING: Direct routing phone number.
18
+ CALLING_PLAN: Microsoft Calling Plan phone number.
19
+ OPERATOR_CONNECT: Operator Connect phone number.
20
+ """
21
+
22
+ DIRECT_ROUTING = "directRouting"
23
+ CALLING_PLAN = "callingPlan"
24
+ OPERATOR_CONNECT = "operatorConnect"
25
+
26
+
27
+ class ActivationState(str, Enum):
28
+ """Phone number activation state.
29
+
30
+ Attributes:
31
+ ACTIVATED: Number is active and ready for use.
32
+ ASSIGNMENT_PENDING: Number assignment is in progress.
33
+ ASSIGNMENT_FAILED: Number assignment failed.
34
+ UPDATE_PENDING: Number update is in progress.
35
+ UPDATE_FAILED: Number update failed.
36
+ """
37
+
38
+ ACTIVATED = "activated"
39
+ ASSIGNMENT_PENDING = "assignmentPending"
40
+ ASSIGNMENT_FAILED = "assignmentFailed"
41
+ UPDATE_PENDING = "updatePending"
42
+ UPDATE_FAILED = "updateFailed"
43
+
44
+
45
+ class AssignmentStatus(str, Enum):
46
+ """Phone number assignment status.
47
+
48
+ Attributes:
49
+ UNASSIGNED: Number is not assigned to any user or resource.
50
+ USER_ASSIGNED: Number is assigned to a user.
51
+ CONFERENCE_ASSIGNED: Number is assigned to a conference bridge.
52
+ VOICE_APPLICATION_ASSIGNED: Number is assigned to a voice application.
53
+ """
54
+
55
+ UNASSIGNED = "unassigned"
56
+ USER_ASSIGNED = "userAssigned"
57
+ CONFERENCE_ASSIGNED = "conferenceAssigned"
58
+ VOICE_APPLICATION_ASSIGNED = "voiceApplicationAssigned"
59
+
60
+
61
+ class AssignmentCategory(str, Enum):
62
+ """Phone number assignment category.
63
+
64
+ Attributes:
65
+ PRIMARY: Primary phone number for the user.
66
+ PRIVATE: Private line number.
67
+ ALTERNATE: Alternate phone number.
68
+ """
69
+
70
+ PRIMARY = "primary"
71
+ PRIVATE = "private"
72
+ ALTERNATE = "alternate"
73
+
74
+
75
+ class OperationStatus(str, Enum):
76
+ """Async operation status from Graph API.
77
+
78
+ Attributes:
79
+ NOT_STARTED: Operation has not started.
80
+ RUNNING: Operation is in progress.
81
+ SUCCEEDED: Operation completed successfully.
82
+ FAILED: Operation failed.
83
+ SKIPPED: Operation was skipped.
84
+ UNKNOWN_FUTURE_VALUE: Reserved for future values.
85
+ """
86
+
87
+ NOT_STARTED = "notStarted"
88
+ RUNNING = "running"
89
+ SUCCEEDED = "succeeded"
90
+ FAILED = "failed"
91
+ SKIPPED = "skipped"
92
+ UNKNOWN_FUTURE_VALUE = "unknownFutureValue"
93
+
94
+
95
+ class OperationNumber(BaseModel):
96
+ """Status of a phone number in an async operation.
97
+
98
+ Attributes:
99
+ resource_location: URL to the phone number resource.
100
+ status: Current status of the operation for this number.
101
+ """
102
+
103
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
104
+
105
+ resource_location: str = Field(alias="resourceLocation")
106
+ status: OperationStatus
107
+
108
+
109
+ class AsyncOperation(BaseModel):
110
+ """Async operation polling response from Graph API.
111
+
112
+ Represents the response from polling a long-running phone number operation.
113
+
114
+ Attributes:
115
+ odata_context: OData context annotation from Graph API.
116
+ odata_type: OData type annotation from Graph API.
117
+ id: Unique identifier for the operation (not present in polling responses).
118
+ created_date_time: When the operation was created.
119
+ status: Overall status of the operation.
120
+ numbers: Status of individual phone numbers in the operation.
121
+ """
122
+
123
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
124
+
125
+ odata_context: str | None = Field(alias="@odata.context", default=None)
126
+ odata_type: str | None = Field(alias="@odata.type", default=None)
127
+ id: str | None = Field(default=None)
128
+ created_date_time: datetime = Field(alias="createdDateTime")
129
+ status: OperationStatus
130
+ numbers: list[OperationNumber] = Field(default=[])
131
+
132
+
133
+ class NumberAssignment(BaseModel):
134
+ """Phone number assignment from Graph API.
135
+
136
+ Represents a phone number in the tenant's inventory with its assignment details.
137
+
138
+ Attributes:
139
+ id: Unique identifier for the number assignment.
140
+ telephone_number: Phone number in E.164 format.
141
+ operator_id: ID of the operator providing the number.
142
+ number_type: Type of the phone number (direct routing, calling plan, etc.).
143
+ activation_state: Current activation state of the number.
144
+ capabilities: List of capabilities (userAssignment, conferenceAssignment, etc.).
145
+ location_id: ID of the emergency location.
146
+ civic_address_id: ID of the civic address.
147
+ network_site_id: ID of the network site.
148
+ assignment_target_id: User or resource account object ID.
149
+ assignment_category: Category of assignment (primary, private, alternate).
150
+ assignment_status: Current assignment status.
151
+ port_in_status: Status of port-in if applicable.
152
+ iso_country_code: ISO country code (e.g., "US").
153
+ city: City associated with the number.
154
+ number_source: Source of the number (online, onPremises).
155
+ supported_customer_actions: Actions the customer can perform on this number.
156
+ reverse_number_lookup_options: Available reverse lookup options.
157
+ """
158
+
159
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
160
+
161
+ id: str
162
+ telephone_number: str = Field(alias="telephoneNumber")
163
+ operator_id: str | None = Field(alias="operatorId", default=None)
164
+ number_type: NumberType = Field(alias="numberType")
165
+ activation_state: ActivationState = Field(alias="activationState")
166
+ capabilities: list[str]
167
+ location_id: str | None = Field(alias="locationId", default=None)
168
+ civic_address_id: str | None = Field(alias="civicAddressId", default=None)
169
+ network_site_id: str | None = Field(alias="networkSiteId", default=None)
170
+ assignment_target_id: str | None = Field(alias="assignmentTargetId", default=None)
171
+ assignment_category: AssignmentCategory | None = Field(
172
+ alias="assignmentCategory", default=None
173
+ )
174
+ assignment_status: AssignmentStatus = Field(alias="assignmentStatus")
175
+ port_in_status: str | None = Field(alias="portInStatus", default=None)
176
+ iso_country_code: str | None = Field(alias="isoCountryCode", default=None)
177
+ city: str | None = Field(default=None)
178
+ number_source: str | None = Field(alias="numberSource", default=None)
179
+ supported_customer_actions: list[str] = Field(
180
+ alias="supportedCustomerActions", default=[]
181
+ )
182
+ reverse_number_lookup_options: list[str] = Field(
183
+ alias="reverseNumberLookupOptions", default=[]
184
+ )
@@ -0,0 +1,26 @@
1
+ """Voice policy model for CSV cache data."""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class Policy(BaseModel):
7
+ """Voice policy from CSV cache.
8
+
9
+ Represents a Teams voice policy exported via PowerShell.
10
+ Used for validation and display since Graph API doesn't expose policy endpoints.
11
+
12
+ Attributes:
13
+ policy_type: Type of policy (e.g., "TeamsCallingPolicy", "TenantDialPlan").
14
+ policy_name: Human-readable policy name.
15
+ policy_id: Unique identifier for the policy.
16
+ description: Optional policy description.
17
+ is_global: Whether this is the global (default) policy.
18
+ """
19
+
20
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
21
+
22
+ policy_type: str = Field(alias="policyType")
23
+ policy_name: str = Field(alias="policyName")
24
+ policy_id: str = Field(alias="policyId")
25
+ description: str | None = Field(alias="description", default=None)
26
+ is_global: bool = Field(alias="isGlobal")
@@ -0,0 +1,45 @@
1
+ """Tenant profile models for multi-tenant CLI configuration."""
2
+
3
+ from enum import Enum
4
+ from uuid import UUID
5
+
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+
9
+ class AuthMethod(str, Enum):
10
+ """Authentication methods supported for tenant profiles.
11
+
12
+ Attributes:
13
+ CERTIFICATE: Certificate-based authentication using X.509 certificate.
14
+ CLIENT_SECRET: Client secret authentication (less secure, for dev/test).
15
+ """
16
+
17
+ CERTIFICATE = "certificate"
18
+ CLIENT_SECRET = "client-secret"
19
+
20
+
21
+ class TenantProfile(BaseModel):
22
+ """Configuration for a Microsoft Entra tenant.
23
+
24
+ Represents a tenant profile stored in config.toml for multi-tenant
25
+ CLI operations. Supports both certificate and client-secret authentication.
26
+
27
+ Attributes:
28
+ name: Profile name used as key in configuration (e.g., "contoso").
29
+ tenant_id: Microsoft Entra tenant ID (UUID).
30
+ client_id: App registration client ID (UUID).
31
+ display_name: Human-readable tenant name for display.
32
+ auth_method: Authentication method (certificate or client-secret).
33
+ cert_path: Path to certificate file (required if auth_method is certificate).
34
+ default_location: Default emergency location name for number assignments.
35
+ """
36
+
37
+ model_config = ConfigDict(extra="forbid")
38
+
39
+ name: str
40
+ tenant_id: UUID
41
+ client_id: UUID
42
+ display_name: str
43
+ auth_method: AuthMethod
44
+ cert_path: str | None = None
45
+ default_location: str | None = None
@@ -0,0 +1,117 @@
1
+ """User configuration models for Teams Phone Graph API responses.
2
+
3
+ These models represent the Teams User Configuration data from Microsoft Graph API.
4
+ All models use `extra="forbid"` for strict contract validation to detect API changes.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from enum import Enum
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field
11
+
12
+
13
+ class AccountType(str, Enum):
14
+ """User account type in Microsoft Teams.
15
+
16
+ Attributes:
17
+ USER: Standard user account.
18
+ RESOURCE_ACCOUNT: Resource account (e.g., auto attendant, call queue).
19
+ GUEST: Guest user account.
20
+ SFB_ON_PREM_USER: Skype for Business on-premises user.
21
+ INELIGIBLE_USER: User not eligible for Teams Phone (e.g., no license).
22
+ UNKNOWN: Unknown account type.
23
+ """
24
+
25
+ USER = "user"
26
+ RESOURCE_ACCOUNT = "resourceAccount"
27
+ GUEST = "guest"
28
+ SFB_ON_PREM_USER = "sfbOnPremUser"
29
+ INELIGIBLE_USER = "ineligibleUser"
30
+ UNKNOWN = "unknown"
31
+
32
+
33
+ class TelephoneNumber(BaseModel):
34
+ """A telephone number assigned to a user.
35
+
36
+ Attributes:
37
+ telephone_number: The phone number in E.164 format (e.g., "+13235533696").
38
+ assignment_category: Assignment category (e.g., "primary", "alternate").
39
+ """
40
+
41
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
42
+
43
+ telephone_number: str = Field(alias="telephoneNumber")
44
+ assignment_category: str = Field(alias="assignmentCategory")
45
+
46
+
47
+ class PolicyAssignmentDetail(BaseModel):
48
+ """Details of a policy assignment to a user.
49
+
50
+ Attributes:
51
+ display_name: Human-readable policy name.
52
+ assignment_type: How the policy was assigned (e.g., "direct", "group").
53
+ policy_id: Unique identifier for the policy.
54
+ group_id: Group ID if assigned via group (optional).
55
+ """
56
+
57
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
58
+
59
+ display_name: str = Field(alias="displayName")
60
+ assignment_type: str = Field(alias="assignmentType")
61
+ policy_id: str = Field(alias="policyId")
62
+ group_id: str | None = Field(alias="groupId", default=None)
63
+
64
+
65
+ class EffectivePolicyAssignment(BaseModel):
66
+ """An effective policy assignment on a user.
67
+
68
+ Represents a single policy type and its assignment details.
69
+
70
+ Attributes:
71
+ policy_type: Type of the policy (e.g., "TeamsCallingPolicy", "TenantDialPlan").
72
+ policy_assignment: Details of how the policy is assigned.
73
+ """
74
+
75
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
76
+
77
+ policy_type: str = Field(alias="policyType")
78
+ policy_assignment: PolicyAssignmentDetail = Field(alias="policyAssignment")
79
+
80
+
81
+ class UserConfiguration(BaseModel):
82
+ """Teams user configuration from Microsoft Graph API.
83
+
84
+ Represents a user's Teams Phone configuration including phone numbers,
85
+ policies, and voice enablement status.
86
+
87
+ Attributes:
88
+ id: User's Entra object ID (GUID).
89
+ user_principal_name: User's UPN (email format).
90
+ tenant_id: Microsoft Entra tenant ID.
91
+ display_name: User's display name.
92
+ account_type: Type of user account.
93
+ is_enterprise_voice_enabled: Whether enterprise voice is enabled.
94
+ feature_types: List of enabled features (e.g., "Teams", "CallingPlan").
95
+ telephone_numbers: List of assigned telephone numbers.
96
+ effective_policy_assignments: List of effective policy assignments.
97
+ created_date_time: When the user configuration was created.
98
+ modified_date_time: When the user configuration was last modified.
99
+ """
100
+
101
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
102
+
103
+ id: str
104
+ user_principal_name: str = Field(alias="userPrincipalName")
105
+ tenant_id: str = Field(alias="tenantId")
106
+ display_name: str | None = Field(alias="displayName", default=None)
107
+ account_type: AccountType = Field(alias="accountType")
108
+ is_enterprise_voice_enabled: bool = Field(alias="isEnterpriseVoiceEnabled")
109
+ feature_types: list[str] = Field(alias="featureTypes", default=[])
110
+ telephone_numbers: list[TelephoneNumber] = Field(
111
+ alias="telephoneNumbers", default=[]
112
+ )
113
+ effective_policy_assignments: list[EffectivePolicyAssignment] = Field(
114
+ alias="effectivePolicyAssignments", default=[]
115
+ )
116
+ created_date_time: datetime | None = Field(alias="createdDateTime", default=None)
117
+ modified_date_time: datetime | None = Field(alias="modifiedDateTime", default=None)
@@ -0,0 +1,21 @@
1
+ """Service Layer - Business logic for Teams Phone operations."""
2
+
3
+ from teams_phone.services.auth_service import CLIENT_SECRET_ENV_VAR, SCOPES, AuthService
4
+ from teams_phone.services.location_service import LocationService
5
+ from teams_phone.services.policy_service import PolicyService
6
+ from teams_phone.services.tenant_service import TenantService
7
+
8
+
9
+ # Note: UserService and NumberService are not exported here to avoid circular import.
10
+ # Import directly:
11
+ # from teams_phone.services.user_service import UserService
12
+ # from teams_phone.services.number_service import NumberService
13
+
14
+ __all__ = [
15
+ "AuthService",
16
+ "CLIENT_SECRET_ENV_VAR",
17
+ "LocationService",
18
+ "PolicyService",
19
+ "SCOPES",
20
+ "TenantService",
21
+ ]