pypi Office365-REST-Python-Client 3.3.1
v3.3.1

one hour ago

Two stories: model types now declare the OData metadata they read — every getter names the member behind it, and a type's schema derives its projections from those declarations instead of a hand-kept list — and the migration layer became a set of portable trees, so an export of mail, calendars, contacts or a whole bundle can be inspected, resumed and imported into another tenant.

✨ Highlights

  • The model declares what it reads. 10,924 getters name the OData member behind them — 4,013 in office365.sharepoint, 6,911 across 1,057 Graph modules — and TypeSchema derives its projections from that index. TypeSchema.of(Web).columns goes 39 → 118 names (.navigations 2 → 50), List 21 → 98, User 21 → 81, Group 13 → 53. Request bodies are unchanged: a declaration says what a response carries, never what a request must send.
  • The same index answers a type's identity, its versions and its roles. @key(...), @version(...), @odata(navigation=True), @odata(system=True), @odata(compare=Compare(format="date")) and a facet= by role (columns_of_facet("permissions")), read from _schema rather than re-scanned.
  • A value declares itself the way an object already did. ClientValue.__init_subclass__ applies dataclass(cls, repr=False) once and reads the schema from those fields, with @dataclass_transform() telling a type checker what the decorator used to.
  • A payload carries only what was set. 117 SharePoint columns are declared readonly from a live write-and-read-back probe — all 37 of File, 43 more on ListItem, 13 on Folder — and List (41 of its 98 columns) and Web (62) answer the same question, so to_json(profile) no longer echoes a server-owned value. hydrate(payload) is the read verb on ClientObject and ClientValue, and JsonProfile.scoped(Model, only=..., without=...) narrows any profile to one model.
  • Mail, calendars and contacts are portable trees. MailFolderSource/MailExportTarget with EmlTreeSource/MailboxTarget, CalendarSource/IcsExportTarget with IcsTreeSource/CalendarTarget, ContactSource/VcfExportTarget with VcfTreeSource/ContactTarget. A sidecar is the model's own JSON plus provenance, mail is verified by mime_digest (Exchange re-serialises every message it stores), and a resumed run skips what is already on disk.
  • A bundle is one directory. BundleTarget/BundleSource in office365.migration.adapters write content/<sha256[:2]>/<sha256>, an append-only manifest.ndjson and a bundle.json derived from it — content-addressed, idempotent, byte-identical on a re-seal — and the same bundle reads back as a source for another target. A fidelity the bundle cannot carry is refused at plan(), not lost at run().
  • Permissions are declared, and a token can be asked what it proves. office365.permissions exports require_permission, declared, Requirement, ResourceName and index; a declaration names the API resource its names belong to; 413 tenant-administration operations, 54 migration operations and the SharePoint everyday objects declare a rung (READ → WRITE → MANAGE → FULL_CONTROL). TokenClaims reads a JWT's audience, scp and roles without verifying it, and catalog(resource) answers what an API actually publishes.
  • Limits and long-running work are data. One quotas module per product, registered at import, @limit composing over a class and limits_of(cls); Operation.stream()/wait() with Retry-After pacing, jitter, progress and cancel, and a partiallySucceeded outcome that is terminal-but-neither.
  • Faster where it is measured. to_json() 62% less time (#1069), parse_datetime 15.6× (#1067), ODataPathBuilder 17.8% (#1068), Progress snapshots 7.7% (#1066).

⚠️ Breaking changes

  • A projection built from the old hand-kept lists asked for navigation properties, which $select drops silently. Any code that read select_all() and friends by hand now reads TypeSchema.of(T).columns; expand_all() is .expansions, select_facet(f) is .columns_of_facet(f), odata_name(a) is .name_of(a) — on a ClientValue too.
  • A ClientValue subclass no longer takes @dataclass: the base applies it once in __init_subclass__, and a second pass over fields the first consumed makes a different class. Drop the decorator.
  • Every exported sidecar and archive is the model's own JSON — team.json is team.to_json(), a mailbox sidecar is Message.to_json() plus three provenance keys. TeamRecord, ChannelRecord, MemberRecord, TabRecord, AppRecord, PagePayload and NavNode/WebNavigation are gone, IcsProfile is IcsDialect, and a tree written by a pre-release build is refused by name instead of importing as empty fields.
  • The permission machinery lives in office365.permissions; office365.directory.permissions is gone. declared(method, flow=...) raises rather than answering an empty requirement when the method names nothing under that flow.
  • SP.User loses the 58 profile getters that duplicated SP.Directory.User, plus Web.site_id and office365.sharepoint.workflow.task; MigrationItem no longer carries author_id/editor_id (they are item.author.user_id), and the old keywords raise TypeError.
  • Approval columns answer an SPModerationStatusType member, not the raw integer, so a comparison against a literal reads false; SitePage.moderation_status is readonly as well.
  • The lockfile and the dev tooling need Python 3.11 (the wheel still installs on 3.8): every patched release of filelock and tornado declares requires-python >=3.11.

🐛 Notable fixes

  • order_by never reached the service: QueryOptions emitted its Python attribute names, so a sorted read asked for $order_by, which Graph answers 200 to and ignores. Standard options are spelled as the specification spells them (#1063).
  • A small file was never attached to a message that already exists: the <3 MB inline path only registered a child on the collection, and that child rides the message create.
  • A $select read narrowed every later read of the object it was made on: ReadEntityQuery appended its properties to the object's own query_options.
  • A page payload no longer carries an emptied nested value: SitePageFieldsData(Title="x") serialises as {"Title": "x"}, not {"Title": "x", "CoAuthState": {}}, so a farm that never had that property does not refuse the create (#1060).
  • List.export() exported files: the loop compared the wire's number against FileSystemObjectType.File and 0 == File is False. A multi-value column can be updated ({"results": [...]}, the only shape either write accepts), a team site is no longer returned before it exists, and a page's web parts ask for the cast that names the type.
  • A throttled request waits the interval the service asked for — execute_query_retry retried on its own backoff and ignored Retry-After. to_json() no longer emits __-prefixed bookkeeping (__etag, __odata_type), and DateTimeTimeZone.parse() reads a Graph dict, so EventCollection.add(start={...}) works.
  • The term store no longer refuses the type casts the payload carries, activating a directory role no longer announces the base type name, and SignIn.location reads the location rather than the status.
  • The 3.0.0 serverRelativeUrl → server_relative_url rename is documented as breaking, with the migration shown (#1037).

📄 Full changelog: CHANGELOG.md  ·  📦 pip install --upgrade office365-rest-python-client

Don't miss a new Office365-REST-Python-Client release

NewReleases is sending notifications on new releases.