Skip to content

Commit e19bea3

Browse files
committed
chore: commit pending changes
1 parent bd1deeb commit e19bea3

61 files changed

Lines changed: 523 additions & 214 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/onedrive/excel/read_table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
client = GraphClient(tenant=test_tenant).with_username_and_password(test_client_id, test_username, test_password)
1919

2020
# Find an Excel file
21-
items = client.me.drive.search("*.xlsx").execute_query()
21+
items = client.me.drive.search("xlsx").execute_query()
2222
if len(items) == 0:
2323
sys.exit("No Excel files found. Upload one first.")
2424

examples/onedrive/files/analytics.py

Lines changed: 24 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
"""
2-
File analytics — view activity stats, get activities by time interval,
3-
and check item views/modifications over time.
2+
File analytics — view activity stats and access patterns.
43
5-
Shows who accessed a file, when, and how often. Useful for adoption
6-
tracking, content governance, and understanding which files are
7-
most actively used in a drive.
8-
9-
Requires delegated permission ``Files.Read`` and ``Analytics.Read``.
10-
11-
https://learn.microsoft.com/en-us/graph/api/itemanalytics-get
12-
https://learn.microsoft.com/en-us/graph/api/driveitem-getactivitiesbyinterval
4+
Requires delegated permissions Files.Read and Analytics.Read.
135
"""
146

157
from datetime import datetime, timedelta, timezone
@@ -21,74 +13,31 @@
2113
def main():
2214
client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)
2315

24-
drive = client.me.drive
25-
26-
# -- Step 1: pick a file to analyse (most recent) --
27-
items = drive.root.children.top(20).get().execute_query()
28-
files = [i for i in items if i.is_file]
29-
16+
drive = client.sites.get_by_path("/sites/project").drive
17+
files = [i for i in drive.root.children.top(20).get().execute_query() if i.is_file]
3018
if not files:
31-
print("No files found in OneDrive root. Creating a test file...")
32-
drive.root.upload("analytics_test.txt", b"Analytics test content.").execute_query()
33-
files = drive.root.children.top(20).get().execute_query()
34-
files = [i for i in files if i.is_file]
19+
print("No files found.")
20+
return
3521

3622
target = files[0]
37-
print(f"Analysing: {target.name} (size: {target.size or 0} bytes)\n")
38-
39-
# -- Step 2: get analytics for the item --
40-
try:
41-
analytics = target.analytics.get().execute_query()
42-
if analytics:
43-
print("Item analytics:")
44-
if hasattr(analytics, "all_time") and analytics.all_time:
45-
a = analytics.all_time
46-
print(f" All time: {a.access_count or 0} accesses, {a.view_count or 0} views")
47-
print(f" {a.share_count or 0} shares, {a.download_count or 0} downloads")
48-
if hasattr(analytics, "last_seven_days") and analytics.last_seven_days:
49-
l7 = analytics.last_seven_days
50-
print(f" Last 7d : {l7.access_count or 0} accesses, {l7.view_count or 0} views")
51-
else:
52-
print(" (analytics returned no data — may need Analytics.Read permission)")
53-
except Exception as e:
54-
print(f" (analytics not available: {e})")
55-
56-
# -- Step 3: get activity by interval (last 30 days) --
57-
now = datetime.now(timezone.utc)
58-
start = now - timedelta(days=30)
59-
end = now
60-
61-
print("\nActivity by day for the last 30 days:")
62-
try:
63-
activities = target.get_activities_by_interval(
64-
start_dt=start,
65-
end_dt=end,
66-
interval="day",
67-
).execute_query()
68-
69-
print(f" {len(activities)} days with activity")
70-
for act in activities:
71-
dt = act.start_date_time.strftime("%Y-%m-%d") if act.start_date_time else "?"
72-
access = act.access_count or 0
73-
act_view = act.view_count or 0
74-
act_down = act.download_count or 0
75-
act_share = act.share_count or 0
76-
print(f" {dt} accesses={access:>3} views={act_view:>3} downloads={act_down:>3} shares={act_share:>2}")
77-
except Exception as e:
78-
print(f" (activity by interval not available: {e})")
79-
80-
# -- Step 4: item activity (who did what) --
81-
print("\nRecent activities on this file:")
82-
try:
83-
activities_v2 = target.get_activities_by_interval(
84-
start_dt=start,
85-
end_dt=end,
86-
interval="day",
87-
).execute_query()
88-
if activities_v2:
89-
print(" (see activity timeline above — details available via activity insight resources)")
90-
except Exception as e:
91-
print(f" {e}")
23+
print(f"File: {target.name}")
24+
25+
analytics = target.analytics.select(["allTime"]).get().execute_query()
26+
if analytics.all_time:
27+
a = analytics.all_time
28+
print(f" All time: {a.accessCount or 0} accesses, {a.viewCount or 0} views, {a.shareCount or 0} shares")
29+
if analytics.last_seven_days:
30+
l7 = analytics.last_seven_days
31+
print(f" Last 7d: {l7.accessCount or 0} accesses, {l7.viewCount or 0} views")
32+
33+
activities = target.get_activities_by_interval(
34+
start_dt=datetime.now(timezone.utc) - timedelta(days=30),
35+
end_dt=datetime.now(timezone.utc),
36+
interval="day",
37+
).execute_query()
38+
print(f"\nActivity ({len(activities)} days with activity):")
39+
for act in activities:
40+
print(f" {act.start_date_time.date()} accesses={act.accessCount or 0} views={act.viewCount or 0}")
9241

9342

9443
if __name__ == "__main__":

examples/onedrive/lists/manage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
# 1. Create a document library
2525
list_name = create_unique_name("ProjectAssets")
26-
lib = client.sites.root.lists.add(list_name, ListTemplateType.documentLibrary).execute_query()
26+
lib = client.sites.root.lists.add(list_name, ListTemplateType.genericList).execute_query()
2727
print(f"List created: {lib.display_name}")
2828

2929
# 2. Add a text column
@@ -32,7 +32,7 @@
3232

3333
# 3. Create an item with custom column value
3434
item = lib.items.add(Title="Q4 Report", Category="Finance").execute_query()
35-
print(f" Item: {item.display_name} (id: {item.id})")
35+
print(f" Item: {item.name} (id: {item.id})")
3636

3737
# 4. Read items back
3838
items = lib.items.get().execute_query()

examples/onedrive/sites/get_site.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"""
1212

1313
from office365.graph_client import GraphClient
14-
from tests import test_client_id, test_password, test_tenant, test_username
14+
from tests import test_client_id, test_password, test_tenant, test_tenant_name, test_username
1515

1616
client = GraphClient(tenant=test_tenant).with_username_and_password(test_client_id, test_username, test_password)
1717

@@ -20,7 +20,7 @@
2020
print(f"Root site: {root.display_name} ({root.web_url})")
2121

2222
# 2. Get site by URL
23-
site = client.sites.get_by_url(f"https://{test_tenant}.sharepoint.com/sites/team").get().execute_query()
23+
site = client.sites.get_by_url(f"https://{test_tenant_name}.sharepoint.com/sites/project").get().execute_query()
2424
print(f"Team site: {site.display_name} (id: {site.id})")
2525

2626
# 3. Followed sites

generator/.checkpoints/SharePoint.xml.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

office365/migration/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# from office365.migration import (
2+
# MigrationAssessor,
3+
# TenantAssessor,
4+
# MigrationEngine,
5+
# Exporter,
6+
# SyncEngine,
7+
# )

office365/migration/base.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
office365.migration.base
3+
~~~~~~~~~~~~~~~~~~~~~~~~
4+
Shared types, enums, and the MigrationQuery base class.
5+
6+
MigrationQuery extends ClientQuery so all migration operations
7+
plug into the existing deferred execution pipeline:
8+
operation.execute_query()
9+
operation.execute_query_retry()
10+
operation.after_execute(callback)
11+
"""
12+
13+
from __future__ import annotations
14+
15+
from dataclasses import dataclass, field
16+
from enum import Enum
17+
18+
# ── Enums ─────────────────────────────────────────────────────────────────────
19+
20+
21+
class ConflictResolution(str, Enum):
22+
SKIP = "skip"
23+
OVERWRITE = "overwrite"
24+
RENAME = "rename"
25+
26+
27+
class MigrationMode(str, Enum):
28+
FULL = "full"
29+
INCREMENTAL = "incremental"
30+
UPSERT = "upsert"
31+
32+
33+
class ExportFormat(str, Enum):
34+
FILESYSTEM = "filesystem"
35+
PARQUET = "parquet"
36+
CSV = "csv"
37+
JSON = "json"
38+
DELTA = "delta"
39+
40+
41+
# ── Value types ───────────────────────────────────────────────────────────────
42+
43+
44+
@dataclass
45+
class MigrationItem:
46+
"""Unit of work flowing through the migration pipeline."""
47+
48+
source_path: str
49+
dest_path: str
50+
size_bytes: int = 0
51+
item_type: str = "file"
52+
status: str = "pending"
53+
error: Exception | None = None
54+
55+
56+
@dataclass
57+
class MigrationStats:
58+
total: int = 0
59+
success: int = 0
60+
skipped: int = 0
61+
errors: int = 0
62+
bytes_transferred: int = 0
63+
64+
def summary(self) -> str:
65+
mb = self.bytes_transferred / 1024 / 1024
66+
return (
67+
f"Total: {self.total} | Success: {self.success} | "
68+
f"Skipped: {self.skipped} | Errors: {self.errors} | "
69+
f"Transferred: {mb:.1f}MB"
70+
)
71+
72+
73+
@dataclass
74+
class MigrationOptions:
75+
conflict_resolution: ConflictResolution = ConflictResolution.SKIP
76+
preserve_timestamps: bool = True
77+
preserve_permissions: bool = False
78+
preserve_versions: bool = False
79+
include_patterns: list[str] = field(default_factory=list)
80+
exclude_patterns: list[str] = field(default_factory=list)
81+
batch_size: int = 100

office365/onedrive/analytics/item_action_stat.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ class ItemActionStat(ClientValue):
1111

1212
actionCount: int | None = None
1313
actorCount: int | None = None
14+
accessCount: int | None = None
15+
viewCount: int | None = None
16+
shareCount: int | None = None
17+
downloadCount: int | None = None
1418

1519
@property
1620
def entity_type_name(self) -> str:

office365/onedrive/base_item.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from datetime import datetime
4-
from typing import TYPE_CHECKING, Optional
4+
from typing import TYPE_CHECKING, Any, Optional, Self
55

66
from office365.directory.permissions.identity_set import IdentitySet
77
from office365.entity import Entity
@@ -103,6 +103,12 @@ def last_modified_date_time(self) -> datetime:
103103
"""Gets the lastModifiedDateTime property"""
104104
return self.properties.get("lastModifiedDateTime", datetime.min)
105105

106+
def set_property(self, name: str, value: Any, persist_changes: bool = True) -> Self:
107+
super().set_property(name, value, persist_changes)
108+
if name == "parentReference":
109+
pass
110+
return self
111+
106112
def get_property(self, name, default_value=None):
107113
if default_value is None:
108114
property_mapping = {

office365/onedrive/drives/drive.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from office365.runtime.paths.resource_path import ResourcePath
1717
from office365.runtime.queries.create_entity import CreateEntityQuery
1818
from office365.runtime.queries.function import FunctionQuery
19+
from office365.runtime.types.odata_property import odata
1920

2021

2122
class Drive(BaseItem):
@@ -95,6 +96,7 @@ def drive_type(self) -> Optional[str]:
9596
"""
9697
return self.properties.get("driveType", None)
9798

99+
@odata(name="sharepointIds")
98100
@property
99101
def sharepoint_ids(self) -> SharePointIds:
100102
"""Returns identifiers useful for SharePoint REST compatibility."""

0 commit comments

Comments
 (0)