Skip to content

feat(snowflake): add pure-Python Snowflake SQL API adapter#25

Merged
hampsterx merged 1 commit into
masterfrom
feat/snowflake-extra
Jun 18, 2026
Merged

feat(snowflake): add pure-Python Snowflake SQL API adapter#25
hampsterx merged 1 commit into
masterfrom
feat/snowflake-extra

Conversation

@hampsterx

@hampsterx hampsterx commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a [snowflake] extra and create_snowflake_client / create_async_snowflake_client so Lambdas can query Snowflake without the 75MB snowflake-connector-python. The adapter wraps the pure-Python snowflake-sql-api client (httpx), which cold-starts in ~1.2s vs the connector's 8-20s.
  • Keypair credentials load from AWS Secrets Manager by default, with environment-variable and explicit-argument overrides. Resolution is field-by-field with strict explicit > env > Secrets Manager precedence and source-aware passphrase pairing (a key is only ever paired with a passphrase from its own source).
  • Applies NUI session defaults (TIMEZONE=Pacific/Auckland, role NUI_LAMBDA), both overridable, and wires a redacting query-logging hook that logs the statement text plus bind-parameter count, never the bind values, JWTs, key material, or the passphrase.
  • Sync client is the default; an async factory mirrors it for Lambdas already on an event loop.
  • The dependency is a pre-release git-SHA pin of the public snowflake-sql-api repo, kept out of [all] so nothing pulls the git dependency implicitly. It will be repinned to a PyPI release once that package ships.

WEEK_START is intentionally not a session default: the Snowflake SQL API rejects it in the request parameters field (HTTP 400, verified live) and is stateless per statement, so it cannot be applied per call. Set it on the Snowflake user/role default if a consumer needs Monday-first weeks.

Backwards compatibility

Additive only: a new optional extra and a new module. No behavior change for existing consumers; nothing installs the extra implicitly. The nui_lambda_shared_utils deprecation shim re-exports the new surface.

Review

Internal: Pass 1 (bugs) clean, Pass 2 (polish) 5 items auto-fixed. External (cross-family Codex + Kimi, two rounds): no major defects; an explicit-key-vs-env precedence bug and an empty-key edge case were caught and fixed with regression tests; both reviewers confirmed credential precedence, redaction, sync/async parity, kwargs assembly, the missing-extra guard, and lazy-export wiring are correct.

Changes

 AGENTS.md                                   |   1 +
 README.md                                   |  19 +
 nui_lambda_shared_utils/snowflake_client.py |   3 +
 nui_shared_utils/__init__.py                |  12 +
 nui_shared_utils/snowflake_client.py        | 422 +++++++++++++++++++++
 pyproject.toml                              |   6 +
 requirements-test.txt                       |   5 +-
 setup.py                                    |   7 +
 tests/test_snowflake_client.py              | 559 ++++++++++++++++++++++++++++
 9 files changed, 1033 insertions(+), 1 deletion(-)

Test plan

  • 36 adapter unit tests, 100% coverage on the new module (moto for Secrets Manager)
  • Full package suite green (555 passed)
  • black + mypy clean on the new module; python -m build + twine check pass with the git-pin extra
  • Live smoke against dev Snowflake through the adapter: TIMEZONE default applied and overridable, multi-row fetch, server-side bind round-trip, redaction confirmed
  • CI matrix green on Python 3.9-3.12

Summary by CodeRabbit

  • New Features
    • Added a Snowflake SQL API integration with synchronous and asynchronous client factories.
    • Implemented credential resolution with clear precedence across explicit inputs, environment variables, and AWS Secrets Manager.
    • Added a redacting query-logging hook that truncates SQL and avoids bind value/secret leakage.
  • Documentation
    • Updated installation extras and added Snowflake (SQL API) usage examples.
  • Tests
    • Added an offline test suite covering credential precedence, validation, redaction behavior, sync/async parity, and missing-extra error handling.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 288f4cda-3529-4f02-80eb-c137ce35e153

📥 Commits

Reviewing files that changed from the base of the PR and between 79f21df and 7220955.

📒 Files selected for processing (9)
  • AGENTS.md
  • README.md
  • nui_lambda_shared_utils/snowflake_client.py
  • nui_shared_utils/__init__.py
  • nui_shared_utils/snowflake_client.py
  • pyproject.toml
  • requirements-test.txt
  • setup.py
  • tests/test_snowflake_client.py
✅ Files skipped from review due to trivial changes (2)
  • nui_lambda_shared_utils/snowflake_client.py
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • pyproject.toml
  • setup.py
  • requirements-test.txt
  • nui_shared_utils/init.py

Walkthrough

Adds a new nui_shared_utils.snowflake_client module wrapping the optional snowflake-sql-api library. It provides get_snowflake_credentials() with explicit/env/Secrets Manager precedence, a redacting_query_logger() hook, and create_snowflake_client()/create_async_snowflake_client() factories. Package lazy exports and a nui_lambda_shared_utils shim are wired accordingly, with packaging declarations and a 564-line test suite included.

Changes

Snowflake SQL API Integration

Layer / File(s) Summary
Optional dependency declarations
pyproject.toml, setup.py, requirements-test.txt
Declares the snowflake optional-dependency group in both packaging files and the test requirements file, each pinning snowflake-sql-api to a specific git commit as a pre-release pin.
Adapter module: types, defaults, helpers, and credential resolution
nui_shared_utils/snowflake_client.py (lines 1–223)
Defines QueryHook type, guards the optional import with SNOWFLAKE_SQL_API_AVAILABLE, declares DEFAULT_TIMEZONE/DEFAULT_ROLE/DEFAULT_SECRET_NAME constants, and implements get_snowflake_credentials() with explicit-arg > env > Secrets Manager precedence, PEM-to-bytes coercion, and tier-paired passphrase resolution.
Redacting logger, kwargs builder, and sync/async factories
nui_shared_utils/snowflake_client.py (lines 225–422)
Implements redacting_query_logger() (logs truncated SQL and bind-param counts only), _client_kwargs() (adapter-managed fields win over passthrough kwargs), and create_snowflake_client()/create_async_snowflake_client() factories that wire all three together.
Package wiring, compat shim, and docs
nui_shared_utils/__init__.py, nui_lambda_shared_utils/snowflake_client.py, AGENTS.md, README.md
Registers the four new callables in the lazy-export map and optional-submodule allowlist; adds TYPE_CHECKING imports; introduces the nui_lambda_shared_utils wildcard-reexport shim; updates AGENTS.md extras list and README with install/usage examples.
Test suite
tests/test_snowflake_client.py
Covers credential precedence and error paths, redacting_query_logger bind-value redaction and SQL truncation, factory defaults/overrides/async parity and parameter immutability, e2e redaction asserting no key material appears in logs, missing-extra ImportError guard, and lazy-export/shim wiring correctness.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 A Snowflake now drifts through the warren so bright,
Credentials resolved with precedence just right,
The redacting hook hides all the secrets away,
Sync and async both ready for query day.
No bind values leaked, no PEM keys in sight—
The bunny ships Snowflake with tests holding tight! ❄️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding a Snowflake SQL API adapter. It is specific, clear, and directly reflects the primary purpose of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/snowflake-extra

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • LINEAR integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
README.md (1)

204-219: ⚡ Quick win

Make overrideability explicit in the Snowflake example.

The section mentions NUI defaults, but the snippet doesn’t show how to override them. Adding one explicit override example keeps this feature clearly generic for non-NUI users.

Proposed README tweak
-Pure-Python Snowflake client (no `snowflake-connector-python`), keypair auth via
-Secrets Manager, with NUI session defaults (`TIMEZONE=Pacific/Auckland`, role
-`NUI_LAMBDA`) and a redacting query-logging hook.
+Pure-Python Snowflake client (no `snowflake-connector-python`), keypair auth via
+Secrets Manager, with NUI session defaults (`TIMEZONE=Pacific/Auckland`, role
+`NUI_LAMBDA`) that you can override via `timezone=` and `role=`, plus a
+redacting query-logging hook.
@@
-client = create_snowflake_client(warehouse="COMPUTE_WH", database="ANALYTICS")
+client = create_snowflake_client(
+    warehouse="COMPUTE_WH",
+    database="ANALYTICS",
+    timezone="UTC",
+    role="MY_APP_ROLE",
+)

Based on learnings: "Before adding new features, verify they are generic and useful for any Lambda project, not NUI-specific; if NUI-specific, document clearly and provide configuration options."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 204 - 219, The Snowflake example code snippet shows
NUI-specific defaults like TIMEZONE and role but does not demonstrate how to
override them, making the example appear NUI-specific rather than generic. Add
an explicit example to the code snippet showing how to override at least one of
the default settings (such as timezone or role) when calling
create_snowflake_client, so that non-NUI users can clearly see these defaults
are configurable and the client is suitable for generic Lambda projects. This
demonstrates the overrideability of the NUI defaults and keeps the feature
generic.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@README.md`:
- Around line 204-219: The Snowflake example code snippet shows NUI-specific
defaults like TIMEZONE and role but does not demonstrate how to override them,
making the example appear NUI-specific rather than generic. Add an explicit
example to the code snippet showing how to override at least one of the default
settings (such as timezone or role) when calling create_snowflake_client, so
that non-NUI users can clearly see these defaults are configurable and the
client is suitable for generic Lambda projects. This demonstrates the
overrideability of the NUI defaults and keeps the feature generic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b45ffee0-6dda-4f40-8f86-ffe9d968aba3

📥 Commits

Reviewing files that changed from the base of the PR and between 2f8577b and e00f28c.

📒 Files selected for processing (9)
  • AGENTS.md
  • README.md
  • nui_lambda_shared_utils/snowflake_client.py
  • nui_shared_utils/__init__.py
  • nui_shared_utils/snowflake_client.py
  • pyproject.toml
  • requirements-test.txt
  • setup.py
  • tests/test_snowflake_client.py

@hampsterx

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hampsterx hampsterx force-pushed the feat/snowflake-extra branch from e00f28c to 79f21df Compare June 17, 2026 21:20
@hampsterx

Copy link
Copy Markdown
Contributor Author

Applied the README nitpick: the Snowflake example now overrides timezone= and role= to show the NUI defaults are configurable for any account.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (1)

68-75: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify that [all] does not include Snowflake.

The install block currently says [all] is “All integrations”, but this PR intentionally keeps Snowflake out of [all]. Please update wording to prevent confusion and ImportError when users expect Snowflake to be included.

Suggested docs tweak
-pip install nui-python-shared-utils[all]          # All integrations
+pip install nui-python-shared-utils[all]          # Core optional integrations (excludes Snowflake)
 pip install nui-python-shared-utils[powertools]   # AWS Powertools only
 ...
 pip install nui-python-shared-utils[snowflake]    # Snowflake SQL API client

Based on learnings: Use specific extras ([slack], [elasticsearch]) rather than [all] when installing the package to optimize Lambda bundle size.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 68 - 75, The documentation currently describes the
[all] installation option as "All integrations" without clarifying that
Snowflake is intentionally excluded from this bundle. Update the comment next to
the [all] pip install command to explicitly state that it includes all
integrations except Snowflake, and optionally add a note recommending users
choose specific extras like [slack], [elasticsearch], [database], [jwt], and
[powertools] individually instead of [all] to optimize Lambda bundle size, with
[snowflake] installed separately if needed.

Source: Learnings

tests/test_snowflake_client.py (1)

84-560: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add pytest markers to categorize this suite (unit/integration/slow).

The tests are unmarked right now. Please apply the required pytest markers so selective runs (-m ...) and CI partitioning remain consistent.

As per coding guidelines: **/tests/test_*.py: Use @pytest.mark.unit, @pytest.mark.integration, and @pytest.mark.slow pytest markers to categorize tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_snowflake_client.py` around lines 84 - 560, Add pytest markers to
categorize tests in the test_snowflake_client.py file. Apply `@pytest.mark.unit`
to the majority of tests in TestGetCredentials, TestRedactingLogger,
TestCreateClient (except test_real_sync_client_constructs and
test_real_async_client_constructs), TestMissingExtra, and TestPackageWiring
classes since they mock dependencies and test isolated functionality. Apply
`@pytest.mark.integration` to tests like test_secrets_manager in
TestGetCredentials, test_real_sync_client_constructs and
test_real_async_client_constructs in TestCreateClient, and the entire
TestRedactionEndToEnd class as these construct real objects or test end-to-end
behavior. Add markers as class-level decorators using `@pytest.mark.unit` or
`@pytest.mark.integration` on each test class, or as method-level decorators on
individual test methods if different tests within a class require different
categorizations.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nui_shared_utils/snowflake_client.py`:
- Around line 132-139: The `private_key` parameter in the three public functions
`get_snowflake_credentials()`, `create_snowflake_client()`, and
`create_async_snowflake_client()` is incorrectly typed as `Optional[bytes]` when
it should accept both bytes and str (PEM text format) to match the
implementation in the `_as_key_bytes()` helper. Update the type annotation for
the `private_key` parameter in all three functions from `Optional[bytes]` to
`Optional[Union[bytes, str]]`. Ensure that Union is imported from the typing
module if it is not already present at the top of the file.

In `@README.md`:
- Line 225: The markdown linting error MD031 indicates missing blank lines
around a fenced code block. Locate the fenced code block in README.md that
contains the text "already on an event loop. Requires the `[snowflake]` extra."
and ensure there is a blank line immediately before the opening fence and a
blank line immediately after the closing fence to comply with markdownlint
formatting requirements.

---

Outside diff comments:
In `@README.md`:
- Around line 68-75: The documentation currently describes the [all]
installation option as "All integrations" without clarifying that Snowflake is
intentionally excluded from this bundle. Update the comment next to the [all]
pip install command to explicitly state that it includes all integrations except
Snowflake, and optionally add a note recommending users choose specific extras
like [slack], [elasticsearch], [database], [jwt], and [powertools] individually
instead of [all] to optimize Lambda bundle size, with [snowflake] installed
separately if needed.

In `@tests/test_snowflake_client.py`:
- Around line 84-560: Add pytest markers to categorize tests in the
test_snowflake_client.py file. Apply `@pytest.mark.unit` to the majority of tests
in TestGetCredentials, TestRedactingLogger, TestCreateClient (except
test_real_sync_client_constructs and test_real_async_client_constructs),
TestMissingExtra, and TestPackageWiring classes since they mock dependencies and
test isolated functionality. Apply `@pytest.mark.integration` to tests like
test_secrets_manager in TestGetCredentials, test_real_sync_client_constructs and
test_real_async_client_constructs in TestCreateClient, and the entire
TestRedactionEndToEnd class as these construct real objects or test end-to-end
behavior. Add markers as class-level decorators using `@pytest.mark.unit` or
`@pytest.mark.integration` on each test class, or as method-level decorators on
individual test methods if different tests within a class require different
categorizations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5cc747eb-1568-4309-aafe-174f9b5c27de

📥 Commits

Reviewing files that changed from the base of the PR and between e00f28c and 79f21df.

📒 Files selected for processing (9)
  • AGENTS.md
  • README.md
  • nui_lambda_shared_utils/snowflake_client.py
  • nui_shared_utils/__init__.py
  • nui_shared_utils/snowflake_client.py
  • pyproject.toml
  • requirements-test.txt
  • setup.py
  • tests/test_snowflake_client.py
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • requirements-test.txt
  • pyproject.toml
  • nui_lambda_shared_utils/snowflake_client.py
  • nui_shared_utils/init.py

Comment thread nui_shared_utils/snowflake_client.py
Comment thread README.md
- Add [snowflake] extra + create_snowflake_client / create_async_snowflake_client
  so Lambdas query Snowflake without the 75MB snowflake-connector-python
  (pure-Python snowflake-sql-api over httpx)
- Load keypair from Secrets Manager by default (env + explicit overrides), strict
  explicit > env > secret precedence with source-aware passphrase pairing
- Apply NUI session defaults (TIMEZONE=Pacific/Auckland, role NUI_LAMBDA, both
  overridable); redacting query-log hook never logs bind values, JWTs, or key material
- Sync client is the default; an async factory mirrors it for event-loop Lambdas
- Dependency is a pre-release git-SHA pin of the public snowflake-sql-api repo,
  kept out of [all] so nothing pulls it implicitly

WEEK_START is intentionally not a session default: the SQL API rejects it in the
request parameters (HTTP 400) and is stateless per statement, so it cannot be set
per call. Set it on the Snowflake user/role default if needed.

Backwards compatible: additive only (new extra + new module). No change to existing
consumers; nui_lambda_shared_utils shim re-exports the new surface.
@hampsterx hampsterx force-pushed the feat/snowflake-extra branch from 79f21df to 7220955 Compare June 17, 2026 21:30
@hampsterx

Copy link
Copy Markdown
Contributor Author

Addressed the review:

  • README [all] wording: clarified to # Core optional integrations (excludes Snowflake) (Snowflake is intentionally out of [all]).
  • pytest markers: added module-level pytestmark = pytest.mark.unit (repo convention; these tests are all offline/fast).
  • private_key type: widened to Optional[Union[bytes, str]].
  • README MD031: not reproduced (markdownlint-cli2 reports README.md clean; flagged file is a pre-existing doc, out of scope).

@hampsterx hampsterx merged commit d0af0b6 into master Jun 18, 2026
9 checks passed
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.

1 participant