6 Commits

Author SHA1 Message Date
Alberto Schiabel f67d565743 refactor(python): consolidate path construction from untrusted input (#4144)
This PR:

- centralizes filesystem path construction for API-provided slugs and
filenames in `composio.utils.safe_path`
- rejects traversal, Windows-invalid names, invalid Unicode, and
overlong encoded filenames before creating directories or writing files
- normalizes trusted roots consistently and routes both Python download
paths through the shared helpers
- adds a fail-closed AST guard for new dynamic path construction,
including direct `Path(...)` calls
- isolates provider initialization from the real home directory and
makes the home-write guard report changes without deleting them
- removes the obsolete download filename wrapper

## Verification

- `pytest -q`: 1,248 passed, 47 skipped
- repository-configured Ruff checks and formatting passed for every
changed Python file
- targeted mypy checks passed for the changed helpers and tests
2026-08-18 13:07:07 +02:00
jkomyno 5e57815af1 fix(json-schema): accept and preserve dynamic object content across converters
Property-less objects (`{ type: "object" }`, `properties: {}`) were being
collapsed to a strict empty object by the Zod converter and given an implicit
`additionalProperties: false` by the Effect converter, so valid free-form
payloads such as METABASE_POST_API_CARD.dataset_query were rejected at the CLI
boundary. `ToolSchema.parse` separately dropped root `patternProperties` and
rejected a schema-valued root `additionalProperties`.

Dynamic keys are now routed to exactly the schemas that apply to them, and the
shared acceptance contract lives in one checked-in corpus with byte-identical
TypeScript and Python copies.
2026-08-06 20:33:53 +05:30
Alberto Schiabel 09b552aa03 fix(py): disable retries on non-idempotent tool writes to prevent duplicate side effects (#3657)
This PR:

- stops the Python SDK from silently retrying `tools.execute` and
`tools.proxy`, which are non-idempotent POST writes. The
Stainless-generated client retries POSTs by default (`max_retries=2`) on
read timeouts, 429, 5xx, and connection errors. A read timeout is unsafe
to retry — the request may already be in flight on the backend — so a
retry can duplicate the side effect, e.g. send an email twice. Reported
in https://github.com/ComposioHQ/composio/issues/3586
- routes both write paths through a new `client.without_retries` — a
cached, retry-disabled (`max_retries=0`) clone of the `HttpClient`.
Reads and lists keep the default retries, so resilience is unchanged for
idempotent calls
- overrides `HttpClient.copy()` to re-inject the required `provider`
keyword and re-aliases `with_options`, since the generated `copy()`
rebuilds via `self.__class__(...)` without `provider` — previously
`with_options(max_retries=0)` raised `TypeError` on the subclass
- caches the no-retry sibling instead of cloning per call: each clone
creates a new `ContextVar`, and dynamically-created `ContextVar`s are
never garbage-collected
- adds `tests/test_no_retry_writes.py`: writes hit the transport exactly
once on a retryable 5xx, reads still retry-then-succeed (proving the
scoping), and a regression guard for the `copy()` override
- interim fix only — the durable solution is idempotency keys, tracked
in https://github.com/ComposioHQ/composio/issues/3654 (gated on backend
support), which supersedes this once available

No changeset (Python-only change; changesets are TypeScript-only).
2026-06-25 02:24:27 +04:00
jkomyno 4ea885cd21 feat(py): add tests for cross-sdk webhook handling parity between Python and TypeScriptSDK 2026-01-29 18:20:07 +04:00
angrybayblade e6c3d3f063 chore: linters and formatting 2025-06-17 13:59:52 +05:30
angrybayblade 860b1c444a chore: port python sdk to python/ 2025-06-17 12:58:00 +05:30