Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
1.1.1 - 2026-10-23¶
Added¶
- Python 3.14 is tested in CI and declared in the package classifiers.
1.1.0 - 2026-10-23¶
Changed¶
- Authentication is now handled by sppyte's own
SpPremAuth(sppyte.auth), built directly on pyspnego.requests-ntlm, unmaintained since 2024, is no longer a dependency; runtime dependencies arerequests,pyspnegoandcryptography. Behaviour for existing NTLM users is unchanged. Site.username/passwordare optional (defaultsNone);Site.principalisNonefor single sign-on or a customauth.
Added¶
- Kerberos / Negotiate:
Site(..., protocol="negotiate")(or"kerberos"), with or without credentials; single sign-on uses SSPI on Windows or the current ticket elsewhere.pip install "sppyte[kerberos]"adds the GSSAPI bindings for Linux/macOS. Site(auth=...)accepts anyrequests.auth.AuthBase.- TLS channel bindings (
tls-server-end-point) are sent over HTTPS for servers with Extended Protection (SpPremAuth(send_cbt=False)to disable). - Handshake tests against an in-process fake NTLM server (
tests/fake_ntlm.py).
1.0.0 - 2026-10-23¶
First stable release. Identical in code to 1.0.0b2, which was verified against an on-premises SharePoint farm. Everything below under 1.0.0a1 through 1.0.0b2 applies, relative to 0.2.0; the "Migrating to 1.0" section of the README summarises the breaking changes.
Changed¶
- Development status classifier is now Production/Stable.
1.0.0b2 - 2026-10-23¶
Changed¶
- Importing
sppyte.modelsand callingutils.parse_add_item/parse_add_documentnow emitDeprecationWarning. - All docstrings follow Google style (PEP 257), enforced by ruff's
pydocstylerules;mypyrequires fully typed definitions insppyte.
Added¶
- Documentation site built with MkDocs (
hatch run docs:serve), published to GitHub Pages. Site.__exit__is fully typed;JSON_NOMETADATA/JSON_VERBOSEconstants insppyte.odata.- CI:
hatch fmt --check(CI no longer silently reformats),fail-fast: false, least-privilegepermissions, concurrency cancellation, a build +twine checkjob; the publish workflow verifies the release tag matches the package version and lints/type-checks/tests before building; Dependabot for GitHub Actions and pip.
1.0.0b1 - 2026-10-23¶
First beta: the planned 1.0 feature set is complete.
Added¶
- Opt-in retry:
Site(retry=True | RetryOptions(...))retries reads (GETrequests,get_page, CAMLquery) on connection errors, timeouts and HTTP 429/502/503/504 with exponential backoff and full jitter, honouringRetry-After(and stopping when it exceedsmax_delay). Writes and$batchare never retried.Site.request(retry=...)overrides per call.sppyte.retryexposesRetryOptions,is_transient,with_retry,RETRYABLE_STATUSES.
1.0.0a3 - 2026-10-23¶
Added¶
- CAML queries:
List.query(view_xml, *, paging_info=None, params=None, digest=None)(GetItems),List.iter_queryandList.query_all, which page by the last item'sIdusing<RowLimit>. $batch:Site.batch()returns aBatchbuilder (get/post/merge/patch/delete/add,send()); consecutive writes share one changeset; oneBatchResponseper sub-request withstatus,ok,headers,body,jsonanderror. Sub-requests must be on the site's host.sppyte.batchalso exposesbuild_batch_body,parse_batch_response,multipart_boundary.- Public REST path helpers:
List.path,Library.folder_path(*subfolders),Library.file_path(file_name, *subfolders).
1.0.0a2 - 2026-10-23¶
Changed (breaking)¶
Library.add_folderreturns theSP.Folderobject (dict) instead of theExistsbool.Library.add_documentreturns theSP.Fileobject (dict) instead of theUniqueIdstring.List.add_attachmentreturns theSP.Attachmentobject (dict) instead of echoingsp_id.List.get_contentsandLibrary.list_contentsreturn aPage(alistsubclass withnext_link).Library.get_documentsendsAccept: application/octet-stream.
Added¶
- Paging:
Page,Site.get_page(next_link)(same-host only),List.iter_pages/iter_items/get_all_items,Library.iter_pages/iter_files/get_all_files, all withmax_pages=. Library.list_folders(*subfolders, params=None).- Streaming downloads:
Library.iter_document(...)andLibrary.download(file_name, dest, *subfolders). Library.add_documentaccepts a path (str/os.PathLike) and streams it; newoverwrite=keyword.sppyte.odata.parse_page/follow_pages;sppyte.utils.parse_object.ruffis pinned forhatch fmtso local and CI lint agree.
1.0.0a1 - 2026-10-23¶
Changed (breaking)¶
List.add_itemreturns the created item as adict(includingId) instead of the integer ID.Library.list_contents(params, *subfolders)is nowlist_contents(*subfolders, params=None);paramsis keyword-only.ListandLibraryare no longer context managers and have noconnect()/close(); theSiteis the sole owner of the HTTP session.- Non-2xx responses raise
SharePointError(orAuthErrorfor 401/403) instead of a barerequests.HTTPError. Both subclassrequests.HTTPError, so existing handlers keep working.SharePointErrorexposesstatus,code,message,retry_afterandresponse. - Requests time out after 30 seconds by default (
Site(timeout=...);Nonedisables). Site(...),site.list()andsite.library()no longer perform network I/O; the session is created on first request and the form digest on first write (orconnect()).Site.form_digestis a read-only property.List.add_attachmentsendsContent-Type: application/octet-stream.Sitevalidates its arguments and raisesConfigurationError(aValueError) for a scheme-less or path-bearinghost, asite_relative_urlwithout a leading/, a full URL as the relative URL, or an empty username.
Added¶
- Managed form digest: cached until shortly before SharePoint's
FormDigestTimeoutSeconds, shared between threads with a lock, refreshed and the write retried once on 403 when the body can be re-sent (a non-seekable stream is not retried).Site.digest(),Site.invalidate_digest(). digest=keyword on every write method to supply your own digest (sent as-is, no retry).List.item_typecached property; the entity type is looked up once perListinstead of on every write.Site(domain=...)for the NTLM domain,verify=/cert=TLS options, andsession=to supply a pre-configuredrequests.Session.Site.request(..., digest=True | "value")for extension calls that need a digest.Library.server_relative_url(*parts).- Exceptions
SppyteError,ConfigurationError,AuthError,SharePointError,SharePointErrorDetails; helperssppyte.errors.parse_sharepoint_error,parse_retry_after,raise_for_status. sppyte.utils.parse_context_infoandparse_created_item.__repr__onSite,ListandLibrary(password not shown).- Modules
sppyte.site,sppyte.lists,sppyte.libraries,sppyte.odata;sppyte.modelsremains as a re-export shim.
Fixed¶
- A write rejected with 403 for a stale digest is now retried with a fresh digest; previously the stale header was re-sent.
- Replaying a request with a consumed file body is no longer attempted (it sent an empty body).
Deprecated¶
sppyte.models(import fromsppyte),SessionError(no longer raised),sppyte.utils.parse_add_item(superseded byparse_created_item).
0.2.0 - 2026-10-23¶
Fixed¶
Library.delete_folderbuilt the path_api/web/_api/web/...and failed on every call.Library.add_foldersent a library-relativeServerRelativeUrl; it now includes the site relative URL like every otherLibrarymethod, so it works on sub-sites (/sites/...).- List titles, file names and folder paths are now escaped for OData string literals
(apostrophes doubled, segments percent-encoded), so names containing
',#,%,?or&no longer break the request URL. List.add_itemandList.update_itemno longer modify the mapping passed in.Site.sessionandSite.form_digestare instance attributes instead of class attributes.- The
Paramstype alias now matches what the methods accept (a mapping or a sequence of pairs), andget_contents/list_contentsare typed as returninglist[dict]. - pytest no longer puts
src/sppyteitself onsys.path.
Added¶
sppyte.utils.odata_literalandsppyte.utils.odata_pathescaping helpers.py.typedmarker so type checkers use the package's annotations.CHANGELOG.md.- Test coverage of
Site,ListandLibraryagainst a mocked HTTP layer (responses).
Changed¶
Library.add_documentis typed to acceptbytes | IO[bytes](it always did; the README said so but the signature did not).List.add_item/List.update_itemaccept anyMapping, not justdict.