Skip to content

feat: expand Python SDK coverage and clean up repo scaffolding#12

Open
saksham-nexla wants to merge 15 commits intomainfrom
feat/improved-python-sdk-and-add-typescript-sdk
Open

feat: expand Python SDK coverage and clean up repo scaffolding#12
saksham-nexla wants to merge 15 commits intomainfrom
feat/improved-python-sdk-and-add-typescript-sdk

Conversation

@saksham-nexla
Copy link
Copy Markdown
Member

@saksham-nexla saksham-nexla commented Feb 6, 2026

Summary

This PR substantially expands the Nexla Python SDK and cleans up repository scaffolding so the repo houses only the Python SDK going forward.

Python SDK enhancements

  • New resource modules: api_keys, auth_parameters, auth_templates, clusters, cluster_endpoints, connectors, custom_data_flows, dashboard_transforms, data_credentials_groups, flow_triggers, notification_settings, notification_types, org_tiers, quarantine_settings, resource_parameters, service_keys, user_settings, user_tiers, validators, vendors, vendor_endpoints
  • Request/response Pydantic v2 models for every new resource
  • Expanded base-resource helpers (access control, lifecycle)
  • New raw operations module for low-level API access
  • Bug fixes: always clone the request before retry to avoid consumed-body errors; change approval_requests reject method from PUT to DELETE
  • Drop unused resources that were never wired up (tools, tool_sets, mcp_sessions)

Repository cleanup

  • Consolidate the repo to host only the Python SDK: drop packages/, pnpm-*, turbo.json, package.json, .changeset/, docs/ts-sdk/, and the JS-specific CI workflows
  • Update pyproject.toml URLs to nexla-opensource/nexla-sdk-python
  • Drop the redundant black hook in .pre-commit-config.yaml (it fought with ruff-format on edge cases; ruff-format is Black-compatible and was already in the chain)
  • Apply ruff --fix across nexla_sdk/ and tests/, add NotificationSettingBrief to nexla_sdk/models/__init__.py __all__ to clear F401, drop unused result assignments and == True patterns in tests (F841/E712), run ruff format + isort
  • tests/unit/test_parity_tooling.py: skip cleanly when plugin-redoc-0.yaml is absent (the OpenAPI spec is sourced from upstream and not committed)
  • Regenerate Python API MDX docs in docs-site/ against the current SDK surface

Test plan

  • python -m pytest tests/unit → 763 passed, 3 skipped (parity-tool tests skip when the spec is absent locally)
  • pre-commit run -a clean and idempotent across two consecutive runs
  • ruff check nexla_sdk clean
  • from nexla_sdk import NexlaClient smokes cleanly
  • docs-site/scripts/gen_api_docs.py runs successfully against the new SDK surface

- Deleted the coverage report file `lcov.info` from the TypeScript SDK.
- Removed the `test_auth_param_import.py` script that verified auth_parameters imports.
- Deleted the `verify_type_checking.py` script that tested TYPE_CHECKING pattern implementation.
- Added unit tests for `NexlaClient` initialization and model conversion in `tests/unit/test_client_init.py`.
- Updated `package.json` to include additional metadata and scripts for building and testing.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment thread packages/ts-sdk/src/client/http.ts Outdated
Comment thread nexla_sdk/resources/approval_requests.py Outdated
Comment thread .github/workflows/ci-ts.yml Outdated
Comment on lines +31 to +40
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CI workflows install pnpm after setup-node cache, causing cache setup failure

All three TypeScript CI workflows (ci-ts.yml, ci-ts-integration.yml, release-ts.yml) run actions/setup-node@v4 with cache: 'pnpm' before pnpm/action-setup@v4. The actions/setup-node cache feature for pnpm requires pnpm to already be installed so it can run pnpm store path to determine the cache directory. The correct order is: (1) install pnpm via pnpm/action-setup, then (2) setup Node.js with cache. This is documented in both the setup-node and pnpm/action-setup READMEs. The steps should be swapped in all three workflows.

Suggested change
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread README.md Outdated

# Build OpenAPI/admin-routes/SDK parity matrices
python scripts/parity/build_matrices.py \
--admin-routes /Users/sakshammittal/Documents/GitHub/admin-api/config/routes.rb
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Hardcoded local filesystem path in README and parity script violates AGENTS.md security rules

The README.md at line 732 contains a hardcoded local path /Users/sakshammittal/Documents/GitHub/admin-api/config/routes.rb, and scripts/parity/build_matrices.py:230-235 uses the same path as a default. AGENTS.md explicitly states: "Do not hardcode tokens or URLs; read from env". The path should use an environment variable (e.g., NEXLA_ADMIN_API_PATH) with a documented placeholder instead of a developer's local machine path.

Suggested change
--admin-routes /Users/sakshammittal/Documents/GitHub/admin-api/config/routes.rb
--admin-routes "$NEXLA_ADMIN_API_PATH/config/routes.rb"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Merges 3 PRs from main:
- #13: Make DataMapInfo.description and NotificationSetting.resource_id optional
- #14: Add monitoring endpoint methods with explicit params for MCP integration
- #15: Add copy_and_replace_credentials convenience method to FlowsResource

Resolved conflicts in base_resource.py, metrics.py, organizations.py,
and users.py by keeping branch's additional methods while incorporating
main's enhanced audit_log params and new monitoring methods.
The reject endpoint uses DELETE, not PUT. Updated the HTTP method
and added a test assertion to verify the correct method is used.
POST requests with a body failed on retry because the original
request body was already consumed. Now clone on every attempt,
not just retries. Added a test for POST retry with body.
Add TS equivalents for Python SDK methods merged from main:
- FlowsResource: copy_and_replace_credentials convenience method
- OrganizationsResource: get_audit_log
- UsersResource: get_audit_log
Add 36 new tests (29 Python, 7 TypeScript) covering methods from
merged PRs #13, #14, #15 and their TS SDK mirrors:

Python:
- base_resource get_audit_log filter/pagination params (6 tests)
- metrics publish_raw, resource_flow_metrics, flow_metrics_summary (4)
- organizations audit_log explicit params, flow_status_metrics (5)
- users audit_log params, account_metrics aggregate, flow_status_metrics (6)
- flows get_flow_logs, search_flow_logs, active_flows_metrics, run_status (8)

TypeScript:
- flows copy_and_replace_credentials edge cases (3)
- organizations/users audit_log query params and empty responses (4)
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment on lines 31 to 33
response = self._make_request(
"PUT", f"/self_signup_requests/{request_id}/approve"
"POST", f"/self_signup_requests/{request_id}/approve"
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 approve_request uses POST but generated operation map and TS SDK use PUT

The approve_request method was changed from PUT to POST at nexla_sdk/resources/self_signup.py:32, with a comment claiming "Backend routes use POST." However, the auto-generated operation map in this same PR (nexla_sdk/generated/operation_map.py:462) defines approve_self_sign_up_request as method: 'PUT', and the TS SDK generated from the same OpenAPI spec also uses PUT (packages/ts-sdk/src/resources/generated/self_signup_admin.ts:19). This creates an inconsistency within the PR: either the Python SDK is sending the wrong HTTP method (POST instead of PUT), or the operation map and TS SDK are wrong. Either way, the Python SDK and TS SDK will behave differently for the same operation.

Prompt for agents
Resolve the inconsistency between the Python SDK, the generated operation map, and the TypeScript SDK for the approve_self_sign_up_request operation. In nexla_sdk/resources/self_signup.py:31-33 the method is POST. In nexla_sdk/generated/operation_map.py:462 the method is PUT. In packages/ts-sdk/src/resources/generated/self_signup_admin.ts:19 the method is PUT. All three should agree. If the backend truly requires POST, update the operation map and regenerate the TS SDK. If the OpenAPI spec (PUT) is correct, revert self_signup.py back to PUT.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread nexla_sdk/resources/organizations.py Outdated
Comment on lines +491 to +496
path = f"{self._path}/{org_id}/custodians"
data = self._serialize_data(payload)
response = self._make_request("PUT", path, json=data)
if isinstance(response, list):
return [CustodianUser.model_validate(item) for item in response]
return []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 add_custodians HTTP method silently changed from POST to PUT, breaking API contract

The add_custodians method's HTTP method was changed from POST to PUT (nexla_sdk/resources/organizations.py:493). The previous implementation used POST which matched the OpenAPI operation add_org_custodians (defined as method: 'POST' in nexla_sdk/generated/operation_map.py:407). The PUT method corresponds to update_org_custodians which has replace/update semantics, not additive semantics. A new reset_custodians method was introduced using POST (line 503), but this means the old add_custodians now silently performs a replace (PUT) instead of an add (POST). Existing callers expecting additive behavior will get replace behavior, potentially removing existing custodians.

Prompt for agents
In nexla_sdk/resources/organizations.py, the add_custodians method on line 493 was changed from POST to PUT, but the OpenAPI operation add_org_custodians (in nexla_sdk/generated/operation_map.py line 407) is defined as POST. Either: (1) Revert add_custodians to use POST (matching the OpenAPI spec and preserving backward compatibility), and keep reset_custodians as POST renamed to something like replace_custodians using PUT. Or (2) If the intent is truly to have add_custodians use PUT for additive semantics, document this breaking change and ensure the operation map is updated accordingly.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Resolve five conflicts:
- nexla_sdk/models/__init__.py: union both sides; add DocContainerInput.
- nexla_sdk/resources/metrics.py: adopt origin's Union[ResourceType,str] +
  _resolve_enum_value + new flow paths + typed response parsing; keep our
  publish_raw method and Optional[] typings.
- nexla_sdk/resources/organizations.py: combine Optional and Union imports;
  apply origin's get_resource_audit_log enum coercion; keep all our new
  methods and reset_custodians.
- tests/unit/test_metrics.py: take origin's six new tests inside
  TestMetricsResource; keep our test_publish_raw and
  test_get_flow_metrics_summary; drop two stale singular-form tests.
- tests/unit/test_organizations.py: union of imports and new tests.
origin/main refactored FlowsResource.get_run_status from
(resource_type, resource_id, run_id) to (flow_id, run_id), and added
three replacement tests (test_get_run_status_uses_flow_id_path,
test_get_run_status_rejects_deprecated_resource_signature,
test_get_run_status_rejects_string_flow_id). Our pre-merge test was a
duplicate exercising the now-deprecated signature.
Removes JS/TS-only artifacts so this repo houses only the Python SDK:

- packages/ts-sdk/ (entire directory)
- Top-level workspace configs: package.json, pnpm-workspace.yaml,
  pnpm-lock.yaml, turbo.json
- .changeset/ release config and pending entry
- docs/ts-sdk/ (TS-specific architecture/coverage docs)
- .github/workflows/ci-ts.yml, ci-ts-integration.yml, release-ts.yml

Pre-existing fixes bundled in:

- README.md: drop the "TypeScript SDK (New)" section.
- pyproject.toml: update project URLs to nexla-opensource/nexla-sdk-python.
- .gitignore: drop the standalone Node.js section and the
  plugin-redoc-0.yaml entry; keep docs-site Docusaurus ignores since the
  docs site stays in this repo.
- .pre-commit-config.yaml: drop the redundant `black` hook that fought
  with `ruff-format` on a couple of files (ruff-format is Black-compatible
  and was already in the chain).
- nexla_sdk/: apply ruff --fix across the package, add
  NotificationSettingBrief to nexla_sdk/models/__init__.py __all__ to
  resolve the last F401, run ruff format + isort across the codebase.
- tests/: drop unused `result` assignments and `== True` comparisons
  flagged by ruff (12 fixes across test_flows, test_organizations,
  test_users, test_nexsets, etc.).
- tests/unit/test_parity_tooling.py: skip cleanly when plugin-redoc-0.yaml
  is absent (the OpenAPI spec is sourced from upstream and is not
  committed to this repo).
- docs-site/: regenerate Python API MDX docs against the current SDK
  surface via `python3 docs-site/scripts/gen_api_docs.py` (catches up
  on previously stale auto-generated documentation).

docs-site/ stays in this repo and continues to generate the Python API
docs via scripts/gen_api_docs.py.
@saksham-nexla saksham-nexla changed the title feat: improve Python SDK and add TypeScript SDK feat: expand Python SDK coverage and clean up repo scaffolding May 9, 2026
The check_operation_map_sync.py CI step was hard-failing on every PR
because plugin-redoc-0.yaml is sourced from upstream and not committed,
which cancelled the rest of the build-test matrix. Mirror the skipif
pattern already used by tests/unit/test_parity_tooling.py: when the
spec is missing, log a clear message and exit 0 instead of crashing.
add_custodians had been silently changed from POST to PUT, leaving it
as a literal duplicate of update_custodians (same path, same method,
same body). The OpenAPI spec defines add_org_custodians as POST and
update_org_custodians as PUT on /orgs/{org_id}/custodians, so the
SDK now matches:

- add_custodians:    POST   (add_org_custodians)
- update_custodians: PUT    (update_org_custodians)
- remove_custodians: DELETE (remove_org_custodians)

reset_custodians was a renamed copy of the original POST add semantics
and is removed; no callers reference it. API docs are regenerated to
reflect the trimmed surface.
build_matrices.py defaulted --admin-routes to a personal filesystem
path (/Users/sakshammittal/...), which violates AGENTS.md and made the
script unusable for anyone else. The env-var fallback is now strict:
when neither --admin-routes nor NEXLA_ADMIN_API_PATH is set, the
script errors out with a clear message instead of silently pointing
at a path that doesn't exist on the user's machine. README updates
the parity-tooling example to use $NEXLA_ADMIN_API_PATH.
The PR review surfaced two silent HTTP-method changes (add_custodians
POST->PUT and approve_request PUT->POST) that no test caught. Add
explicit method assertions so future drift is caught at unit-test
time:

- test_add_custodians_uses_post / update / remove (POST / PUT / DELETE)
- approve_request now asserts last_request method == POST, locking
  the divergence already documented inline (backend uses POST while
  the OpenAPI spec advertises PUT in some versions).
pyproject.toml declares requires-python = ">=3.8", but typing.Annotated
only landed in 3.9. The Python 3.8 CI job was failing with
ImportError: cannot import name 'Annotated' from 'typing'. Pull it
from typing_extensions instead, which pydantic v2 already pulls in
transitively.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants