Skip to content

Commit 0742891

Browse files
committed
refactor: time-based poll_for_status with failed detection and deferred execution preserved
1 parent f53023a commit 0742891

2 files changed

Lines changed: 35 additions & 60 deletions

File tree

examples/teams/clone_team.py

Lines changed: 9 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,36 @@
11
"""
22
Clone a team — copy its channels, apps, tabs, settings, and/or members.
33
4-
Uses the ``ClonableTeamParts`` enum as a bitset so you can select
5-
exactly what to copy. The clone operation is async; this example
6-
polls the source team's ``operations`` collection until it completes.
4+
The clone operation is async; ``execute_query_and_wait`` polls until
5+
the operation completes.
76
8-
Typical provisioning pattern: bootstrap a new project from a template
9-
team that already has the right channel structure and app setup.
10-
11-
Requires delegated permission ``Team.Create`` or ``Group.ReadWrite.All``.
12-
13-
Note: the SDK's ``team.clone()`` helper is a stub that doesn't pass
14-
the required body parameters yet. This example uses the underlying
15-
``ServiceOperationQuery`` directly to send the proper payload.
16-
17-
https://learn.microsoft.com/en-us/graph/api/team-clone
7+
Requires delegated permission Team.Create or Group.ReadWrite.All.
188
"""
199

20-
import sys
21-
import time
22-
2310
from office365.graph_client import GraphClient
2411
from office365.teams.clonableteamparts import ClonableTeamParts
25-
from office365.teams.team import Team
2612
from tests import test_client_id, test_password, test_tenant, test_username
2713

2814

29-
def poll_clone_completion(team: Team, max_wait_sec: int = 120) -> bool:
30-
"""Poll the team's operations list waiting for a clone to succeed/fail."""
31-
for attempt in range(1, (max_wait_sec // 10) + 1):
32-
time.sleep(10)
33-
ops = team.operations.get().execute_query()
34-
for op in ops:
35-
print(f" [{attempt * 10}s] Operation status: {op.status}")
36-
if op.status == "succeeded":
37-
return True
38-
elif op.status == "failed":
39-
err = op.properties.get("error", {})
40-
print(f" Error: {err.get('message', 'unknown')}")
41-
return False
42-
return False
43-
44-
4515
def main():
4616
client = GraphClient(tenant=test_tenant).with_username_and_password(test_client_id, test_username, test_password)
4717

48-
# — Step 1: pick a source team —
4918
teams = client.teams.get().top(1).execute_query()
50-
if len(teams) == 0:
51-
sys.exit("No source team found.")
19+
if not teams:
20+
print("No source team found.")
21+
return
5222

5323
source_team = teams[0]
5424
print(f"Source team: {source_team.display_name}")
5525

56-
print("Cloning (this may take 60+ seconds)...\n")
57-
target_team = source_team.clone(
26+
print("Cloning...")
27+
source_team.clone(
5828
f"{source_team.display_name}_cloned",
5929
f"{source_team.display_name}_cloned",
6030
ClonableTeamParts.channels,
6131
source_team.visibility,
6232
).execute_query_and_wait()
33+
print("Clone completed.")
6334

6435

6536
if __name__ == "__main__":

office365/teams/operations/async_operation.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,42 @@ class TeamsAsyncOperation(Entity):
3030
def poll_for_status(
3131
self,
3232
status_type: TeamsAsyncOperationStatus = TeamsAsyncOperationStatus.succeeded,
33-
max_polling_count: int = 5,
34-
polling_interval_secs: int = 15,
33+
timeout_sec: int = 180,
34+
polling_interval: int = 15,
3535
success_callback: Callable[[TeamsAsyncOperation], None] | None = None,
3636
failure_callback: Callable[[TeamsAsyncOperation], None] | None = None,
3737
) -> Self:
38-
"""Poll to check for completion of an async Teams create call
38+
"""Poll to check for completion of an async Teams operation.
3939
4040
Args:
41-
polling_interval_secs (int):
42-
max_polling_count (int):
43-
status_type (TeamsAsyncOperationStatus): The status to wait for
44-
success_callback ((TeamsAsyncOperation)-> None): Called on success with the operation
45-
failure_callback ((TeamsAsyncOperation)-> None): Called on timeout
41+
status_type: The status to wait for (default succeeded)
42+
timeout_sec: Maximum seconds to wait (default 180)
43+
polling_interval: Seconds between polls (default 15)
44+
success_callback: Called on success with the populated operation
45+
failure_callback: Called on timeout or failed status
4646
"""
47+
deadline = time.time() + timeout_sec
4748

48-
def _poll_for_status(polling_number: int) -> None:
49-
if polling_number > max_polling_count:
49+
def _poll():
50+
self.get().after_execute(_verify_status, execute_first=True)
51+
52+
def _verify_status(return_type: TeamsAsyncOperation):
53+
if return_type.status == status_type:
54+
if callable(success_callback):
55+
success_callback(return_type)
56+
return
57+
if return_type.status == TeamsAsyncOperationStatus.failed:
58+
if callable(failure_callback):
59+
failure_callback(return_type)
60+
return
61+
if time.time() >= deadline:
5062
if callable(failure_callback):
5163
failure_callback(self)
52-
else:
53-
raise TimeoutError("The maximum polling count has been reached")
54-
55-
def _verify_status(return_type: TeamsAsyncOperation):
56-
if return_type.status != status_type:
57-
time.sleep(polling_interval_secs)
58-
_poll_for_status(polling_number + 1)
59-
elif callable(success_callback):
60-
success_callback(return_type)
61-
62-
self.get().after_execute(_verify_status, execute_first=True)
64+
return
65+
time.sleep(polling_interval)
66+
_poll()
6367

64-
_poll_for_status(1)
68+
_poll()
6569
return self
6670

6771
@odata(name="attemptsCount")

0 commit comments

Comments
 (0)