Conversation
Adds the standalone Python SDK (import: b2c_tooling_sdk; dist: salesforce-b2c-tooling-sdk) that mirrors the TypeScript b2c-tooling-sdk public surface and interoperates with the B2C CLI (shared auth-session store and config files). Includes auth (all methods), config resolution, OCAPI/SCAPI typed clients, WebDAV, operations, SLAS shopper tokens, a sync facade, docs, and runnable example notebooks. Also adds python/LICENSE (Apache-2.0), python/.gitignore, and a tag-triggered GitHub Actions release workflow (python-v*) that builds the wheel/sdist and attaches them to a GitHub Release for pip installation.
Document installation via git+https from the python branch (and pinned tags) as a temporary arrangement during development, until the package is published to PyPI. Remove the now-unneeded GitHub Release workflow — the git+https install builds straight from the ref and needs no release assets.
Interactive script proposes the next minor version, runs the quality gate, bumps pyproject.toml, commits, tags python-v<version>, and pushes to the fork after confirmation. RELEASE.md now leads with the script; manual steps kept as reference.
get_default_data_dir() now mirrors @oclif/core's Config.dataDir (and the sibling get_b2c_config_directory): $B2C_DATA_DIR | $XDG_DATA_HOME | (win32 %LOCALAPPDATA%) | ~/.local/share, then /b2c. Previously it returned ~/Library/Application Support/@salesforce/b2c-cli on macOS, so find_auth_session could never locate sessions written by the b2c CLI. The identical bug still exists in the TS SDK (session-store.ts getDefaultDataDir).
Five auth/API scenarios (oauth_ocapi, oauth_scapi, basic_webdav, cli_session, slas_shopper) in async + sync, plus matching notebooks, all reading one git-ignored dw.json (dw.example.json is the pseudo-value template).
…irectly The sync facade proxies the whole object graph, so instance.webdav from b2c_tooling_sdk.sync.resolve_config is already blocking. The sample was await-ing webdav.put/get/delete, which raised TypeError on a None result. Call the WebDAV methods directly (no asyncio) and correct the docstring + README note that repeated the wrong 'wrap in asyncio.run' premise.
Adds browser_login_{async,sync}.py + notebook/06-browser-login.ipynb showing
the SDK equivalent of 'b2c auth login <clientId>': create_user_auth_strategy +
get_token_response runs the Authorization Code + PKCE browser flow and persists
the session to the shared auth-sessions store (reusable by the CLI).
Adds multi_env_{async,sync}.py + notebook/07-multi-env.ipynb + a bundled
multi-env.example.json demonstrating named-environment selection from a
multi-config dw.json via ResolveConfigOptions(instance=...). Offline; no
credentials or network required.
… deterministic Read specs from the sibling JS package (packages/b2c-tooling-sdk/specs) instead of vendoring a second copy under python/specs, and stop bundling specs in the wheel/sdist. Generate models with datamodel-code-generator's builtin formatter, then format each file with the project's pinned ruff -- datamodel's black/isort integration silently no-ops on recent toolchains, which made regeneration non-deterministic. Pin datamodel-code-generator so output stays stable. Regenerated models are AST-identical (pure reformat).
…s, and content Add live Jupyter notebooks demonstrating the admin Data APIs with OAuth client-credentials, parsing responses into the SDK's generated Pydantic types: - 10-scapi-catalogs: list catalogs via create_catalogs_backend and a typed product/catalogs/v1 call (SCAPI Data) - 11-ocapi-products: product search + product by id (OCAPI Data) - 12-ocapi-content-assets: list a folder's content + content asset by id (OCAPI Data) SCAPI Admin covers only catalog listing here, so products/content go through the OCAPI Data API; notebooks handle the OCAPI Data allow-list 403 gracefully. Also add the previously-uncommitted SLAS Shopper notebooks (08/09) and document all of them in the samples README.
Add python/CLAUDE.md documenting the Python SDK subproject (commands, architecture, async/sync facade, generated models, conventions), and a using-b2c-tooling-sdk skill guiding consumption of the SDK from scripts and notebooks (install, auth by use case, available clients and operations).
…ing SDK Move the using-b2c-tooling-sdk consumer skill out of the Python package's local .claude/skills into a first-class, installable skills plugin so it ships to consumers. Register it in plugins.json (release packaging), both marketplace manifests, and the CLI skill installer (sources.ts + SkillSet type) so it installs via the plugin marketplace or 'b2c setup skills b2c-python-sdk'. Add the missing references/api-catalog.md the skill points to.
Move the Python package (src, tests, docs, scripts, build/config) from python/ into python/b2c-tooling-sdk/, alongside the sibling python/samples/. Pure relocation.
The Python SDK is headed for SalesforceCommerceCloud/b2c-developer-tooling@main, so update the git-install URL, release badge, and 'python' branch prose in the SDK README, docs/index, and the b2c-python-sdk skill to reference the upstream repo and main branch. Also fix the skill's SDK-development pointer to python/b2c-tooling-sdk/CLAUDE.md after the reorg. RELEASE.md is left untouched — it documents the current fork-based release process. Note: the @main install command only works once the branch is merged upstream.
Remove unused imports, sort imports, and apply ruff format to the offline docs/notebooks so the SDK lint/format gate is green. Autofix only; no behavioral changes. Samples notebooks are left untouched.
…tory The SDK moved into python/b2c-tooling-sdk/; update the samples requirements pin to the new subdirectory path.
Point the samples requirements at SalesforceCommerceCloud/...@main instead of the priandsf fork, matching the consumer install docs. Release machinery (RELEASE.md, release.sh) still targets the fork by design.
The b2c-python-sdk skill set was added to SKILL_SOURCES but the ALL_SKILL_SETS test still expected 5 entries. Include it and bump the expected length to 6.
|
I did a review and there were some good findings but not critical. However we're declaring python 3.10 support here but that's failing the tests. 3.11+ works. I'd suggest just dropping 3.10 instead of resolving it. But we should probably add a CI step to run pytest, ruff, etc. Basic coverage. We can even scope that to run only on PRs with python/* file changes (skipping otherwise). That might be good to add now so future PRs have a baseline regression suite for py For reference these are some of the issues identified but all this looks like eventual followups or nit-picking rather than necessary here (except for the CI stuff):
|
|
Also I highly recommend we get this onboarded into changesets here. all packages use it even if they aren't nodejs (for instance the agent-skills and docs site here are in changesets). https://github.com/changesets/changesets/blob/main/docs/versioning-apps.md |
3.10 was declared as supported but failed the deploy-timeout test suite; 3.11+ works reliably. Bumping the floor also raises ruff/mypy's target-version, which auto-converts datetime.timezone.utc to the 3.11+ datetime.UTC alias across the codebase.
The Python SDK had no CI coverage at all - no tests, linting, type checking, or supported-version matrix ran on PRs. Adds a workflow scoped to python/b2c-tooling-sdk/** and python/samples/** changes that runs ruff/mypy/pytest across Python 3.11-3.13, plus a cheap import-smoke job for the (otherwise untested, credential-requiring) sample scripts.
Extracting a code-version or site-archive ZIP joined entry names onto the output directory with plain os.path.join, so a malicious archive entry named e.g. ../../../etc/evil could write outside the destination (zip-slip). Adds resolve_zip_entry_path(), which resolves the joined path and raises if it would escape the base directory, and uses it at both extraction sites (code download, site-archive export).
_AuthMiddleware and _ScapiAuthMiddleware tracked already-retried requests in a set/dict keyed by "method + URL", which persists for the middleware instance's lifetime. Once any call to an endpoint retried once, every later call to that same endpoint was permanently blocked from retrying again - even on a genuine, later token expiry. Switches both to a weakref.WeakSet/WeakKeyDictionary keyed by the httpx.Request object itself, matching the TS SDK's WeakSet<Request> design: a fresh Request is built per outer call, so tracking naturally resets per call while still preventing a double-retry within one call.
…ally The session store wrote the temp file with the process's default mode via Path.write_text, then chmodded it to 0o600 afterwards - leaving a window where the file (containing refresh tokens) could be world/group readable under a permissive umask. Opens the temp file directly via os.open(..., 0o600) instead, so the restrictive mode applies from the first open() syscall, matching the TS backend's writeFileSync mode option.
make build-sync invoked scripts/build_sync.py, which was never written - an early plan considered generating the sync layer via unasync, but a runtime facade (sync/_runner.py, sync/_proxy.py) was built instead. The target survived the pivot as dead, confusing tooling.
Calling b2c_tooling_sdk.sync from inside an already-running event loop (every Jupyter cell, regardless of await usage) blocked that loop with no explanation - warn instead so notebooks keep working but the condition is visible. Mixing direct `await` use and `sync` use of the same object raised a bare cross-loop RuntimeError far from the cause - append an actionable hint. syncify() built a fresh SyncProxy per call with no __eq__/__hash__, breaking identity/equality between the async object and its synced wrapper - cache proxies by target identity and add __eq__/__hash__ that delegate to the wrapped object. Also fixes a PKCE code example in the b2c-python-sdk skill that referenced a nonexistent AuthCredentials constructor instead of PkceOAuthConfig.
Registers python/b2c-tooling-sdk as a private, unpublished workspace package (@salesforce/b2c-tooling-sdk-python), following the same "versioning app" pattern already used for docs/ and skills/: it exists purely so Changesets can version and changelog the Python SDK. A new sync script (scripts/sync-python-sdk-version.mjs), run as part of `pnpm run version`, propagates the changeset-bumped version into pyproject.toml and version.py's fallback - fixing a pre-existing drift where the fallback was stuck at 0.3.0 while pyproject.toml was at 0.4.0. release.sh no longer prompts for or bumps a version itself; it reads whatever Changesets already committed to pyproject.toml and just tags + pushes. Actual release automation (CI tag/publish) is intentionally not wired up here, since Python releases ship from a separate `python` branch/fork rather than `main` - tagging stays a manual release.sh step. Updates RELEASE.md/CLAUDE.md/AGENTS.md to describe the new flow.
…llback server dual-stack
webdav.py was sending the pre-middleware request body to auth.fetch instead
of the (possibly rewritten) body on the httpx.Request middleware returned,
silently dropping any body changes middleware made. The OAuth redirect
callback server bound only 127.0.0.1 while the default redirect URI is
http://localhost:{port}; bind localhost (both loopback families) to match
Node's dual-stack listen() default and avoid connection failures where
localhost resolves to ::1 first. Also drops a redundant duplicate
_ZIP_HEADERS definition in download.py.
…er-tooling into python # Conflicts: # .gitignore # AGENTS.md # packages/b2c-tooling-sdk/src/skills/sources.ts # packages/b2c-tooling-sdk/src/skills/types.ts # packages/b2c-tooling-sdk/test/skills/sources.test.ts # pnpm-lock.yaml # pnpm-workspace.yaml
|
@clavery Thanks for the review. I should have addressed all the findings. |
@W-24132874@
What
Adds a Python port of the B2C tooling SDK under
python/b2c-tooling-sdk/, plus supporting samples, documentation, and an agent-skills plugin for consuming it. This is additive — it lives entirely underpython/and does not touch the existing TypeScript packages.Highlights
salesforce-b2c-tooling-sdk(importb2c_tooling_sdk) — a faithful port of@salesforce/b2c-tooling-sdk: async-first with a complete synchronous facade (b2c_tooling_sdk.sync), Python 3.10+.dw.json,~/.mobify,settings.json, multi-env aliases).ClientResult, never raise on 4xx/5xx) and higher-level success-or-raise operations (code deploy, jobs, sites, catalogs, BM users/roles, sandboxes, metrics, logs).auth-sessions.jsonand config files as@salesforce/b2c-cli, so a session created byb2c auth loginworks from Python and vice versa.python/samples/) — runnable scripts and Jupyter notebooks for each scenario; offline, mocked, CI-safe notebooks under the SDK'sdocs/notebooks/.b2c-python-sdkagent-skills plugin — a consumer skill (using-b2c-tooling-sdk) with a full symbol catalog, wired into the marketplace and theb2c setup skillsinstaller.Notes for reviewers
SalesforceCommerceCloud/…@mainand only resolve once this merges tomain(chicken-and-egg by design; PyPI publishing is future work).RELEASE.md/release.shintentionally still describe the interim fork-based tag release process.