nui-python-shared-utils 1.3.2__tar.gz → 1.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nui_python_shared_utils-1.3.2/CLAUDE.md → nui_python_shared_utils-1.4.1/AGENTS.md +7 -4
- nui_python_shared_utils-1.4.1/CLAUDE.md +1 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/PKG-INFO +75 -2
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/README.md +68 -1
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/README.md +4 -0
- nui_python_shared_utils-1.4.1/docs/guides/llm-integration.md +200 -0
- nui_python_shared_utils-1.4.1/docs/guides/snowflake-integration.md +159 -0
- nui_python_shared_utils-1.4.1/nui_lambda_shared_utils/__init__.py +44 -0
- nui_python_shared_utils-1.4.1/nui_lambda_shared_utils/snowflake_client.py +3 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_python_shared_utils.egg-info/SOURCES.txt +10 -0
- nui_python_shared_utils-1.4.1/nui_shared_utils/__init__.py +281 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/cloudwatch_metrics.py +13 -3
- nui_python_shared_utils-1.4.1/nui_shared_utils/llm.py +209 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/powertools_helpers.py +37 -15
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/secrets_helper.py +8 -3
- nui_python_shared_utils-1.4.1/nui_shared_utils/snowflake_client.py +422 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/utils.py +74 -88
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/pyproject.toml +12 -1
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/requirements-test.txt +7 -1
- nui_python_shared_utils-1.4.1/scripts/bench_imports.py +107 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/setup.py +10 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_cloudwatch_metrics.py +13 -13
- nui_python_shared_utils-1.4.1/tests/test_lazy_imports.py +179 -0
- nui_python_shared_utils-1.4.1/tests/test_llm.py +227 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_secrets_helper.py +25 -2
- nui_python_shared_utils-1.4.1/tests/test_snowflake_client.py +600 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_utils.py +79 -70
- nui_python_shared_utils-1.3.2/nui_lambda_shared_utils/__init__.py +0 -25
- nui_python_shared_utils-1.3.2/nui_shared_utils/__init__.py +0 -252
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/.editorconfig +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/.github/workflows/ci.yml +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/.github/workflows/publish.yml +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/.github/workflows/test.yml +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/.markdownlint-cli2.yaml +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/CONTRIBUTING.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/LICENSE +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/MANIFEST.in +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/development/testing.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/getting-started/configuration.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/getting-started/installation.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/getting-started/quickstart.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/guides/cli-tools.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/guides/elasticsearch-integration.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/guides/jwt-authentication.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/guides/lambda-utilities.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/guides/log-processing.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/guides/powertools-integration.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/guides/shared-types.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/docs/guides/slack-integration.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/mypy.ini +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/base_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/cli.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/cloudwatch_metrics.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/config.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/db_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/error_handler.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/es_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/es_query_builder.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/jwt_auth.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/lambda_helpers.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/log_processors.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/powertools_helpers.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/secrets_helper.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_formatter.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_setup/__init__.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_setup/channel_creator.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_setup/channel_definitions.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_setup/setup_helpers.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/timezone.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/utils.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/base_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/cli.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/config.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/db_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/error_handler.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/es_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/es_query_builder.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/jwt_auth.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/lambda_helpers.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/log_processors.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_formatter.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_setup/__init__.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_setup/channel_creator.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_setup/channel_definitions.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_setup/setup_helpers.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/nui_shared_utils/timezone.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/pytest.ini +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/redirect/README.md +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/redirect/pyproject.toml +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/redirect/setup.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/setup.cfg +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/__init__.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_aws_utils.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_base_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_config.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_db_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_error_handler.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_es_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_es_query_builder.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_jwt_auth.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_lambda_helpers.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_log_processors.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_powertools_helpers.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_slack_client.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_slack_formatter.py +0 -0
- {nui_python_shared_utils-1.3.2 → nui_python_shared_utils-1.4.1}/tests/test_timezone.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Canonical agent guidance for this repository. Read by Claude Code (via `CLAUDE.md` import), Codex CLI, and humans. If you only have time to read one doc, read this one.
|
|
4
4
|
|
|
5
5
|
## Quick Links
|
|
6
6
|
|
|
@@ -38,6 +38,7 @@ This is `nui-python-shared-utils`, a Python package providing production-ready u
|
|
|
38
38
|
- **Database** (`db_client.py`) - Connection pooling with retry logic
|
|
39
39
|
- **Metrics** (`cloudwatch_metrics.py`) - Batched CloudWatch publishing with decorators
|
|
40
40
|
- **JWT Authentication** (`jwt_auth.py`) - RS256 token validation for API Gateway Lambdas (uses `rsa` package)
|
|
41
|
+
- **LLM Helper** (`llm.py`) - Generic Anthropic (Claude) client plumbing: API-key/Bedrock auth, forced tool-use call, text call (uses `anthropic`)
|
|
41
42
|
- **Error Handling** (`error_handler.py`) - Retry patterns with exponential backoff
|
|
42
43
|
- **Timezone Utils** (`timezone.py`) - Timezone conversion and formatting utilities
|
|
43
44
|
|
|
@@ -49,6 +50,8 @@ The package uses optional extras to minimize Lambda bundle size:
|
|
|
49
50
|
- `database` - MySQL/PostgreSQL drivers
|
|
50
51
|
- `slack` - Slack SDK
|
|
51
52
|
- `jwt` - RS256 JWT validation (`rsa` package)
|
|
53
|
+
- `snowflake` - Pure-Python Snowflake SQL API client (`snowflake-sql-api`)
|
|
54
|
+
- `llm` - Anthropic (Claude) client helper (`anthropic[bedrock]`, both API-key and Bedrock IAM auth)
|
|
52
55
|
- `all` - All integrations
|
|
53
56
|
- `dev` - Development and testing tools
|
|
54
57
|
|
|
@@ -362,7 +365,7 @@ The package is designed to work well in Lambda layers for sharing across multipl
|
|
|
362
365
|
|
|
363
366
|
This project follows a clear documentation structure:
|
|
364
367
|
|
|
365
|
-
- **[
|
|
368
|
+
- **[AGENTS.md](AGENTS.md)** (this file) - Development workflows, commands, testing strategies (`CLAUDE.md` imports it)
|
|
366
369
|
- **[README.md](README.md)** - User-facing PyPI package description with quick start examples
|
|
367
370
|
- **[docs/](docs/README.md)** - Comprehensive usage guides and references
|
|
368
371
|
- `getting-started/` - Installation, configuration, quick start patterns
|
|
@@ -371,7 +374,7 @@ This project follows a clear documentation structure:
|
|
|
371
374
|
- `templates/` - Configuration file templates (Slack setup YAML)
|
|
372
375
|
- `archive/` - Historical documentation and migration notes
|
|
373
376
|
|
|
374
|
-
**For
|
|
377
|
+
**For coding agents**: The docs/ directory contains detailed usage patterns,
|
|
375
378
|
configuration examples, and integration guides that complement these development instructions.
|
|
376
379
|
|
|
377
380
|
**For new package users**: Start with [docs/getting-started/quickstart.md](docs/getting-started/quickstart.md)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nui-python-shared-utils
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Shared Python utilities for AWS Lambda, CLI tools, and agents with Slack, Elasticsearch, and monitoring integrations
|
|
5
5
|
Home-page: https://github.com/nuimarkets/nui-python-shared-utils
|
|
6
6
|
Author: NUI Markets
|
|
@@ -64,6 +64,10 @@ Requires-Dist: aws-lambda-powertools<4.0.0,>=3.6.0; extra == "powertools"
|
|
|
64
64
|
Requires-Dist: coloredlogs>=15.0; extra == "powertools"
|
|
65
65
|
Provides-Extra: jwt
|
|
66
66
|
Requires-Dist: rsa>=4.9; extra == "jwt"
|
|
67
|
+
Provides-Extra: snowflake
|
|
68
|
+
Requires-Dist: snowflake-sql-api<0.2.0,>=0.1.1; extra == "snowflake"
|
|
69
|
+
Provides-Extra: llm
|
|
70
|
+
Requires-Dist: anthropic[bedrock]<1.0.0,>=0.45.0; extra == "llm"
|
|
67
71
|
Provides-Extra: all
|
|
68
72
|
Requires-Dist: elasticsearch<8.0.0,>=7.17.0; extra == "all"
|
|
69
73
|
Requires-Dist: pymysql>=1.0.0; extra == "all"
|
|
@@ -86,6 +90,8 @@ Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
|
86
90
|
Requires-Dist: build>=0.8.0; extra == "dev"
|
|
87
91
|
Requires-Dist: rsa>=4.9; extra == "dev"
|
|
88
92
|
Requires-Dist: cryptography>=41.0.0; extra == "dev"
|
|
93
|
+
Requires-Dist: snowflake-sql-api<0.2.0,>=0.1.1; extra == "dev"
|
|
94
|
+
Requires-Dist: anthropic[bedrock]<1.0.0,>=0.45.0; extra == "dev"
|
|
89
95
|
Dynamic: author
|
|
90
96
|
Dynamic: home-page
|
|
91
97
|
Dynamic: license-file
|
|
@@ -128,6 +134,7 @@ Production-ready shared Python utilities for AWS Lambda functions, CLI tools, an
|
|
|
128
134
|
- **Database Connections** - Connection pooling, automatic retries, and transaction management
|
|
129
135
|
- **CloudWatch Metrics** - Batched publishing with custom dimensions
|
|
130
136
|
- **JWT Authentication** - RS256 token validation for API Gateway Lambdas (lightweight, no PyJWT needed)
|
|
137
|
+
- **Anthropic (Claude) Helper** - Generic client plumbing for LLM calls: API-key or Bedrock IAM auth, forced tool-use, and text calls (prompts and schemas stay in your code)
|
|
131
138
|
- **Error Handling** - Intelligent retry patterns with exponential backoff
|
|
132
139
|
- **Timezone Utilities** - Timezone handling and formatting
|
|
133
140
|
- **Configurable Defaults** - Environment-aware configuration system
|
|
@@ -158,12 +165,14 @@ Production-ready shared Python utilities for AWS Lambda functions, CLI tools, an
|
|
|
158
165
|
pip install nui-python-shared-utils
|
|
159
166
|
|
|
160
167
|
# With specific extras for optional dependencies
|
|
161
|
-
pip install nui-python-shared-utils[all] #
|
|
168
|
+
pip install nui-python-shared-utils[all] # Core optional integrations (excludes Snowflake and LLM)
|
|
162
169
|
pip install nui-python-shared-utils[powertools] # AWS Powertools only
|
|
163
170
|
pip install nui-python-shared-utils[slack] # Slack only
|
|
164
171
|
pip install nui-python-shared-utils[elasticsearch] # Elasticsearch only
|
|
165
172
|
pip install nui-python-shared-utils[database] # Database only
|
|
166
173
|
pip install nui-python-shared-utils[jwt] # JWT authentication only
|
|
174
|
+
pip install nui-python-shared-utils[snowflake] # Snowflake SQL API client
|
|
175
|
+
pip install nui-python-shared-utils[llm] # Anthropic (Claude) client helper
|
|
167
176
|
```
|
|
168
177
|
|
|
169
178
|
### Basic Configuration
|
|
@@ -291,6 +300,35 @@ async with db.get_connection() as conn:
|
|
|
291
300
|
|
|
292
301
|
**[→ See full database guide](docs/getting-started/quickstart.md#database-connections)**
|
|
293
302
|
|
|
303
|
+
### Snowflake (SQL API)
|
|
304
|
+
|
|
305
|
+
Pure-Python Snowflake client (no `snowflake-connector-python`), keypair auth via
|
|
306
|
+
Secrets Manager, with NUI session defaults (`TIMEZONE=Pacific/Auckland`, role
|
|
307
|
+
`NUI_LAMBDA`) that you can override via `timezone=` and `role=`, plus a redacting
|
|
308
|
+
query-logging hook.
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from nui_shared_utils import create_snowflake_client
|
|
312
|
+
|
|
313
|
+
# Loads account/user/private_key from Secrets Manager ("snowflake-credentials"
|
|
314
|
+
# by default; override with SNOWFLAKE_CREDENTIALS_SECRET or secret_name=).
|
|
315
|
+
# The NUI defaults are overridable, so the client stays generic for any account.
|
|
316
|
+
client = create_snowflake_client(
|
|
317
|
+
warehouse="COMPUTE_WH",
|
|
318
|
+
database="ANALYTICS",
|
|
319
|
+
timezone="UTC", # override the Pacific/Auckland default
|
|
320
|
+
role="MY_APP_ROLE", # override the NUI_LAMBDA default
|
|
321
|
+
)
|
|
322
|
+
rows = client.query("SELECT id, name FROM orders WHERE status = ?", ["confirmed"])
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Sync is the default; use `create_async_snowflake_client(...)` inside a Lambda
|
|
326
|
+
or FastAPI app already on an event loop. Snowflake SQL API bindings use
|
|
327
|
+
positional `?` placeholders, not connector-style `%s` placeholders. Requires
|
|
328
|
+
the `[snowflake]` extra.
|
|
329
|
+
|
|
330
|
+
**[→ See full Snowflake guide](docs/guides/snowflake-integration.md)**
|
|
331
|
+
|
|
294
332
|
### CloudWatch Metrics
|
|
295
333
|
|
|
296
334
|
```python
|
|
@@ -323,6 +361,41 @@ def lambda_handler(event, context):
|
|
|
323
361
|
|
|
324
362
|
**[→ See JWT authentication guide](docs/guides/jwt-authentication.md)**
|
|
325
363
|
|
|
364
|
+
### Anthropic (Claude) Helper
|
|
365
|
+
|
|
366
|
+
Generic plumbing for Claude calls: build a client (API-key or Bedrock IAM), make
|
|
367
|
+
a forced tool-use call or a text call, and get the parsed result back. Prompts,
|
|
368
|
+
tool schemas, model ids, and result post-processing stay in your code.
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
from nui_shared_utils import build_anthropic_client, call_tool
|
|
372
|
+
|
|
373
|
+
# API-key auth: explicit key -> ANTHROPIC_API_KEY env -> Secrets Manager
|
|
374
|
+
client = build_anthropic_client(secret_name="my/anthropic-key")
|
|
375
|
+
# Or Bedrock IAM (no key): build_anthropic_client(mode="bedrock", region="us-east-1")
|
|
376
|
+
|
|
377
|
+
tool = {
|
|
378
|
+
"name": "classify",
|
|
379
|
+
"description": "Classify the text.",
|
|
380
|
+
"input_schema": {
|
|
381
|
+
"type": "object",
|
|
382
|
+
"properties": {"label": {"type": "string"}, "score": {"type": "number"}},
|
|
383
|
+
"required": ["label", "score"],
|
|
384
|
+
"additionalProperties": False,
|
|
385
|
+
},
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
# Forced tool-use; returns the tool's input dict, or None on any model/parse failure.
|
|
389
|
+
result = call_tool(client, tool=tool, prompt="Great product!", model="claude-haiku-4-5", max_tokens=256)
|
|
390
|
+
if result is not None:
|
|
391
|
+
print(result["label"], result["score"])
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Requires the `[llm]` extra. `call_tool` is best-effort (returns `None`, never
|
|
395
|
+
raises); `call_text` returns `{text, input_tokens, output_tokens}`.
|
|
396
|
+
|
|
397
|
+
**[→ See full LLM integration guide](docs/guides/llm-integration.md)**
|
|
398
|
+
|
|
326
399
|
### Error Handling
|
|
327
400
|
|
|
328
401
|
```python
|
|
@@ -35,6 +35,7 @@ Production-ready shared Python utilities for AWS Lambda functions, CLI tools, an
|
|
|
35
35
|
- **Database Connections** - Connection pooling, automatic retries, and transaction management
|
|
36
36
|
- **CloudWatch Metrics** - Batched publishing with custom dimensions
|
|
37
37
|
- **JWT Authentication** - RS256 token validation for API Gateway Lambdas (lightweight, no PyJWT needed)
|
|
38
|
+
- **Anthropic (Claude) Helper** - Generic client plumbing for LLM calls: API-key or Bedrock IAM auth, forced tool-use, and text calls (prompts and schemas stay in your code)
|
|
38
39
|
- **Error Handling** - Intelligent retry patterns with exponential backoff
|
|
39
40
|
- **Timezone Utilities** - Timezone handling and formatting
|
|
40
41
|
- **Configurable Defaults** - Environment-aware configuration system
|
|
@@ -65,12 +66,14 @@ Production-ready shared Python utilities for AWS Lambda functions, CLI tools, an
|
|
|
65
66
|
pip install nui-python-shared-utils
|
|
66
67
|
|
|
67
68
|
# With specific extras for optional dependencies
|
|
68
|
-
pip install nui-python-shared-utils[all] #
|
|
69
|
+
pip install nui-python-shared-utils[all] # Core optional integrations (excludes Snowflake and LLM)
|
|
69
70
|
pip install nui-python-shared-utils[powertools] # AWS Powertools only
|
|
70
71
|
pip install nui-python-shared-utils[slack] # Slack only
|
|
71
72
|
pip install nui-python-shared-utils[elasticsearch] # Elasticsearch only
|
|
72
73
|
pip install nui-python-shared-utils[database] # Database only
|
|
73
74
|
pip install nui-python-shared-utils[jwt] # JWT authentication only
|
|
75
|
+
pip install nui-python-shared-utils[snowflake] # Snowflake SQL API client
|
|
76
|
+
pip install nui-python-shared-utils[llm] # Anthropic (Claude) client helper
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
### Basic Configuration
|
|
@@ -198,6 +201,35 @@ async with db.get_connection() as conn:
|
|
|
198
201
|
|
|
199
202
|
**[→ See full database guide](docs/getting-started/quickstart.md#database-connections)**
|
|
200
203
|
|
|
204
|
+
### Snowflake (SQL API)
|
|
205
|
+
|
|
206
|
+
Pure-Python Snowflake client (no `snowflake-connector-python`), keypair auth via
|
|
207
|
+
Secrets Manager, with NUI session defaults (`TIMEZONE=Pacific/Auckland`, role
|
|
208
|
+
`NUI_LAMBDA`) that you can override via `timezone=` and `role=`, plus a redacting
|
|
209
|
+
query-logging hook.
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from nui_shared_utils import create_snowflake_client
|
|
213
|
+
|
|
214
|
+
# Loads account/user/private_key from Secrets Manager ("snowflake-credentials"
|
|
215
|
+
# by default; override with SNOWFLAKE_CREDENTIALS_SECRET or secret_name=).
|
|
216
|
+
# The NUI defaults are overridable, so the client stays generic for any account.
|
|
217
|
+
client = create_snowflake_client(
|
|
218
|
+
warehouse="COMPUTE_WH",
|
|
219
|
+
database="ANALYTICS",
|
|
220
|
+
timezone="UTC", # override the Pacific/Auckland default
|
|
221
|
+
role="MY_APP_ROLE", # override the NUI_LAMBDA default
|
|
222
|
+
)
|
|
223
|
+
rows = client.query("SELECT id, name FROM orders WHERE status = ?", ["confirmed"])
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Sync is the default; use `create_async_snowflake_client(...)` inside a Lambda
|
|
227
|
+
or FastAPI app already on an event loop. Snowflake SQL API bindings use
|
|
228
|
+
positional `?` placeholders, not connector-style `%s` placeholders. Requires
|
|
229
|
+
the `[snowflake]` extra.
|
|
230
|
+
|
|
231
|
+
**[→ See full Snowflake guide](docs/guides/snowflake-integration.md)**
|
|
232
|
+
|
|
201
233
|
### CloudWatch Metrics
|
|
202
234
|
|
|
203
235
|
```python
|
|
@@ -230,6 +262,41 @@ def lambda_handler(event, context):
|
|
|
230
262
|
|
|
231
263
|
**[→ See JWT authentication guide](docs/guides/jwt-authentication.md)**
|
|
232
264
|
|
|
265
|
+
### Anthropic (Claude) Helper
|
|
266
|
+
|
|
267
|
+
Generic plumbing for Claude calls: build a client (API-key or Bedrock IAM), make
|
|
268
|
+
a forced tool-use call or a text call, and get the parsed result back. Prompts,
|
|
269
|
+
tool schemas, model ids, and result post-processing stay in your code.
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from nui_shared_utils import build_anthropic_client, call_tool
|
|
273
|
+
|
|
274
|
+
# API-key auth: explicit key -> ANTHROPIC_API_KEY env -> Secrets Manager
|
|
275
|
+
client = build_anthropic_client(secret_name="my/anthropic-key")
|
|
276
|
+
# Or Bedrock IAM (no key): build_anthropic_client(mode="bedrock", region="us-east-1")
|
|
277
|
+
|
|
278
|
+
tool = {
|
|
279
|
+
"name": "classify",
|
|
280
|
+
"description": "Classify the text.",
|
|
281
|
+
"input_schema": {
|
|
282
|
+
"type": "object",
|
|
283
|
+
"properties": {"label": {"type": "string"}, "score": {"type": "number"}},
|
|
284
|
+
"required": ["label", "score"],
|
|
285
|
+
"additionalProperties": False,
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# Forced tool-use; returns the tool's input dict, or None on any model/parse failure.
|
|
290
|
+
result = call_tool(client, tool=tool, prompt="Great product!", model="claude-haiku-4-5", max_tokens=256)
|
|
291
|
+
if result is not None:
|
|
292
|
+
print(result["label"], result["score"])
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Requires the `[llm]` extra. `call_tool` is best-effort (returns `None`, never
|
|
296
|
+
raises); `call_text` returns `{text, input_tokens, output_tokens}`.
|
|
297
|
+
|
|
298
|
+
**[→ See full LLM integration guide](docs/guides/llm-integration.md)**
|
|
299
|
+
|
|
233
300
|
### Error Handling
|
|
234
301
|
|
|
235
302
|
```python
|
|
@@ -20,7 +20,9 @@ Component-specific guides for major features:
|
|
|
20
20
|
- **[Lambda Context Helpers](guides/lambda-utilities.md)** - Environment info extraction for logging and metrics
|
|
21
21
|
- **[Slack Integration](guides/slack-integration.md)** - Messaging, formatting, and file uploads
|
|
22
22
|
- **[Elasticsearch Integration](guides/elasticsearch-integration.md)** - Search, bulk indexing, health checks
|
|
23
|
+
- **[Snowflake Integration](guides/snowflake-integration.md)** - SQL API client, Secrets Manager credentials, sync/async usage
|
|
23
24
|
- **[JWT Authentication](guides/jwt-authentication.md)** - RS256 token validation for API Gateway Lambdas
|
|
25
|
+
- **[Anthropic (Claude) Integration](guides/llm-integration.md)** - Client helper for API-key/Bedrock auth, forced tool-use, and text calls
|
|
24
26
|
- **[Log Processing](guides/log-processing.md)** - Kinesis log extraction and ES index naming
|
|
25
27
|
- Database Connections (planned)
|
|
26
28
|
- Error Handling Patterns (planned)
|
|
@@ -124,7 +126,9 @@ When contributing to documentation:
|
|
|
124
126
|
- Lambda context helpers guide (guides/lambda-utilities.md)
|
|
125
127
|
- Slack integration guide (guides/slack-integration.md)
|
|
126
128
|
- Elasticsearch integration guide (guides/elasticsearch-integration.md)
|
|
129
|
+
- Snowflake integration guide (guides/snowflake-integration.md)
|
|
127
130
|
- JWT authentication guide (guides/jwt-authentication.md)
|
|
131
|
+
- Anthropic (Claude) integration guide (guides/llm-integration.md)
|
|
128
132
|
- Shared types reference (guides/shared-types.md)
|
|
129
133
|
- CLI tools guide (guides/cli-tools.md)
|
|
130
134
|
- Testing guide (development/testing.md)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Anthropic (Claude) Integration
|
|
2
|
+
|
|
3
|
+
The LLM helper (`nui_shared_utils.llm`) is generic plumbing for calling
|
|
4
|
+
Anthropic's Claude models from Lambda functions and CLI tools. It builds a
|
|
5
|
+
client, makes a forced tool-use call or a plain text call, and hands back the
|
|
6
|
+
parsed result.
|
|
7
|
+
|
|
8
|
+
It is deliberately thin. Prompts, tool schemas, model ids, and any
|
|
9
|
+
domain-specific processing of the result stay in your code. The helper owns auth,
|
|
10
|
+
the call shape, and result extraction, the parts that were being copy-pasted
|
|
11
|
+
across repos.
|
|
12
|
+
|
|
13
|
+
Install the optional extra:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install "nui-python-shared-utils[llm]"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`anthropic[bedrock]` covers both auth modes (API key and Bedrock IAM). The
|
|
20
|
+
`[bedrock]` sub-extra is what makes `anthropic.AnthropicBedrock` importable.
|
|
21
|
+
|
|
22
|
+
## What this helper is (and is not)
|
|
23
|
+
|
|
24
|
+
| In scope (lives here) | Out of scope (stays in your repo) |
|
|
25
|
+
| --------------------------------------------- | ---------------------------------------------- |
|
|
26
|
+
| Build a client (API key or Bedrock IAM) | Model id selection |
|
|
27
|
+
| Forced tool-use call + `tool_use` extraction | Tool schemas (`input_schema`) |
|
|
28
|
+
| Text call + token-usage extraction | Prompts and system prompts |
|
|
29
|
+
| Best-effort `None` on tool-call failure | Validation / coercion of the returned dict |
|
|
30
|
+
|
|
31
|
+
If you find yourself wanting to add a default model, a prompt, or a tool schema
|
|
32
|
+
to this module, it belongs in the consumer instead.
|
|
33
|
+
|
|
34
|
+
## Building a Client
|
|
35
|
+
|
|
36
|
+
`build_anthropic_client(mode="api_key" | "bedrock", *, api_key=None, secret_name=None, region=None, max_retries=5)`
|
|
37
|
+
|
|
38
|
+
### API-key auth (default)
|
|
39
|
+
|
|
40
|
+
Returns an `anthropic.Anthropic`. The key resolves in order:
|
|
41
|
+
|
|
42
|
+
1. explicit `api_key` argument
|
|
43
|
+
2. `ANTHROPIC_API_KEY` environment variable
|
|
44
|
+
3. AWS Secrets Manager via `secret_name` (read from the `api_key` field)
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from nui_shared_utils import build_anthropic_client
|
|
48
|
+
|
|
49
|
+
# Lambda: key in Secrets Manager
|
|
50
|
+
client = build_anthropic_client(secret_name="my-service/anthropic-key")
|
|
51
|
+
|
|
52
|
+
# Local dev: key in the environment
|
|
53
|
+
client = build_anthropic_client() # reads ANTHROPIC_API_KEY
|
|
54
|
+
|
|
55
|
+
# Explicit (e.g. a key resolved from a CLI keyring or a non-default secret field)
|
|
56
|
+
client = build_anthropic_client(api_key=my_resolved_key)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If your secret stores the key under a field other than `api_key`, resolve it
|
|
60
|
+
yourself and pass `api_key=`:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from nui_shared_utils import get_api_key, build_anthropic_client
|
|
64
|
+
|
|
65
|
+
key = get_api_key("my-service/creds", key_field="anthropic_api_key")
|
|
66
|
+
client = build_anthropic_client(api_key=key)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Bedrock IAM auth
|
|
70
|
+
|
|
71
|
+
Returns an `anthropic.AnthropicBedrock`. No key, the Lambda's IAM role provides
|
|
72
|
+
access. `region` sets `aws_region`; it falls back to `AWS_REGION` /
|
|
73
|
+
`AWS_DEFAULT_REGION` and then to the SDK's own default region resolution.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
client = build_anthropic_client(mode="bedrock", region="us-east-1")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Forced Tool-Use: `call_tool`
|
|
80
|
+
|
|
81
|
+
`call_tool(client, *, tool, prompt, model, max_tokens, system=None) -> dict | None`
|
|
82
|
+
|
|
83
|
+
Forces the model to answer through the named tool (`tool_choice` of type `tool`)
|
|
84
|
+
and returns that tool's `tool_use.input` dict. The helper only forces the call
|
|
85
|
+
and returns the input; it does no validation or coercion itself. Setting
|
|
86
|
+
`"strict": True` on your tool's `input_schema` asks the API to constrain the tool
|
|
87
|
+
input to that schema, but you still own validating and coercing the returned dict
|
|
88
|
+
(value ranges, enum membership, types) in your code.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from nui_shared_utils import build_anthropic_client, call_tool
|
|
92
|
+
|
|
93
|
+
client = build_anthropic_client(secret_name="my-service/anthropic-key")
|
|
94
|
+
|
|
95
|
+
# The tool schema is yours; the helper just forces and extracts it.
|
|
96
|
+
classify_tool = {
|
|
97
|
+
"name": "classify_item",
|
|
98
|
+
"description": "Classify a support message.",
|
|
99
|
+
"strict": True,
|
|
100
|
+
"input_schema": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"properties": {
|
|
103
|
+
"category": {"type": "string", "enum": ["bug", "question", "feature"]},
|
|
104
|
+
"urgency": {"type": "number", "description": "0.0 low to 1.0 urgent"},
|
|
105
|
+
},
|
|
106
|
+
"required": ["category", "urgency"],
|
|
107
|
+
"additionalProperties": False,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
result = call_tool(
|
|
112
|
+
client,
|
|
113
|
+
tool=classify_tool,
|
|
114
|
+
prompt="The export button does nothing and I have a deadline.",
|
|
115
|
+
model="claude-haiku-4-5", # your choice; the helper imposes no default
|
|
116
|
+
max_tokens=256,
|
|
117
|
+
system="You triage inbound support messages.", # optional
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if result is None:
|
|
121
|
+
# Model error, no tool block, or a malformed result. Leave the item
|
|
122
|
+
# unprocessed and let the next run retry it.
|
|
123
|
+
...
|
|
124
|
+
else:
|
|
125
|
+
category = result["category"] # validate/coerce in your code
|
|
126
|
+
urgency = result["urgency"]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Best-effort contract
|
|
130
|
+
|
|
131
|
+
`call_tool` is best-effort and **never raises** on a model or network failure. It
|
|
132
|
+
logs a warning and returns `None` when:
|
|
133
|
+
|
|
134
|
+
- `client.messages.create` raises any exception (transport, timeout, rate-limit, etc.),
|
|
135
|
+
- the response has no `tool_use` block for the named tool,
|
|
136
|
+
- the tool's `input` is not an object,
|
|
137
|
+
- the tool definition is not a dict or has no `name`.
|
|
138
|
+
|
|
139
|
+
This matches the dominant Lambda pattern: a single item failing to enrich must
|
|
140
|
+
not abort the batch. If a caller genuinely needs the exception (rather than a
|
|
141
|
+
`None`-check), that is a deliberate future addition, not the default.
|
|
142
|
+
|
|
143
|
+
## Text Calls: `call_text`
|
|
144
|
+
|
|
145
|
+
`call_text(client, *, prompt, model, max_tokens, system=None) -> dict`
|
|
146
|
+
|
|
147
|
+
Returns `{"text", "input_tokens", "output_tokens"}`. `text` is the concatenation
|
|
148
|
+
of all text content blocks (empty string if the response carried none). Unlike
|
|
149
|
+
`call_tool`, this is **not** best-effort: a transport error propagates, because
|
|
150
|
+
the caller wanted the text or an exception.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from nui_shared_utils import build_anthropic_client, call_text
|
|
154
|
+
|
|
155
|
+
client = build_anthropic_client(mode="bedrock", region="us-east-1")
|
|
156
|
+
|
|
157
|
+
out = call_text(
|
|
158
|
+
client,
|
|
159
|
+
prompt="Summarize this PDF extract in two sentences:\n\n" + extract,
|
|
160
|
+
model="claude-haiku-4-5",
|
|
161
|
+
max_tokens=512,
|
|
162
|
+
)
|
|
163
|
+
print(out["text"])
|
|
164
|
+
print(out["input_tokens"], out["output_tokens"]) # for cost/usage tracking
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Cold Start and the Optional Extra
|
|
168
|
+
|
|
169
|
+
`anthropic` is imported at the top of `nui_shared_utils.llm`, so any use of the
|
|
170
|
+
helper requires the `[llm]` extra. Importing the package itself stays cheap:
|
|
171
|
+
`import nui_shared_utils` does not import this module (and therefore does not
|
|
172
|
+
import `anthropic`) until you first touch an `llm` attribute. This is the same
|
|
173
|
+
PEP 562 lazy-loading behaviour the rest of the package uses to keep Lambda
|
|
174
|
+
cold-start fast (enforced by `tests/test_lazy_imports.py`).
|
|
175
|
+
|
|
176
|
+
Without the extra installed, the top-level lazy exports resolve to `None`
|
|
177
|
+
(matching the other optional integrations), so a missing dependency surfaces as a
|
|
178
|
+
clear `None` rather than a package import failure:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
import nui_shared_utils as nui
|
|
182
|
+
|
|
183
|
+
if nui.build_anthropic_client is None:
|
|
184
|
+
raise RuntimeError("install nui-python-shared-utils[llm] to use the LLM helper")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
A direct `from nui_shared_utils.llm import build_anthropic_client` raises
|
|
188
|
+
`ImportError` when the extra is missing.
|
|
189
|
+
|
|
190
|
+
## Credential Resolution Summary
|
|
191
|
+
|
|
192
|
+
Consistent with the other shared clients (Slack, Elasticsearch, Snowflake):
|
|
193
|
+
|
|
194
|
+
1. explicit argument (`api_key=`)
|
|
195
|
+
2. environment variable (`ANTHROPIC_API_KEY`)
|
|
196
|
+
3. AWS Secrets Manager (`secret_name`, `api_key` field)
|
|
197
|
+
|
|
198
|
+
Bedrock mode uses IAM instead of any of the above. A consumer needing a CLI
|
|
199
|
+
keyring or GPG-backed key resolves it on its own and passes `api_key=`, the
|
|
200
|
+
helper does not pull in a CLI credential loader.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Snowflake Integration
|
|
2
|
+
|
|
3
|
+
The Snowflake adapter wraps `snowflake-sql-api`, a pure-Python SQL API client.
|
|
4
|
+
Use it when a Lambda, CLI, or async API needs Snowflake reads or simple SQL
|
|
5
|
+
execution without bundling `snowflake-connector-python`.
|
|
6
|
+
|
|
7
|
+
Install the optional extra:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install "nui-python-shared-utils[snowflake]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Credential Resolution
|
|
14
|
+
|
|
15
|
+
Credentials resolve in the same order as the other shared clients:
|
|
16
|
+
|
|
17
|
+
1. explicit arguments
|
|
18
|
+
2. `SNOWFLAKE_*` environment variables
|
|
19
|
+
3. AWS Secrets Manager
|
|
20
|
+
|
|
21
|
+
Supported direct environment variables:
|
|
22
|
+
|
|
23
|
+
| Variable | Purpose |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `SNOWFLAKE_ACCOUNT` | Snowflake account locator, for example `xy12345.ap-southeast-2` |
|
|
26
|
+
| `SNOWFLAKE_USER` | Service user |
|
|
27
|
+
| `SNOWFLAKE_PRIVATE_KEY` | Inline PEM private key |
|
|
28
|
+
| `SNOWFLAKE_PRIVATE_KEY_PATH` | Local PEM/DER private key path |
|
|
29
|
+
| `SNOWFLAKE_PRIVATE_KEY_PASSPHRASE` | Optional private key passphrase |
|
|
30
|
+
| `SNOWFLAKE_CREDENTIALS_SECRET` | Secret name override |
|
|
31
|
+
|
|
32
|
+
Secrets Manager values use this JSON shape:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"account": "xy12345.ap-southeast-2",
|
|
37
|
+
"user": "SERVICE_USER",
|
|
38
|
+
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
|
|
39
|
+
"private_key_passphrase": "optional"
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Existing `snowflake-agent-credentials` secrets and service-specific secrets such
|
|
44
|
+
as a future `NUI_ANALYTICS_SVC` secret fit this shape. Pass `secret_name=` when a
|
|
45
|
+
service should not use the default or environment-selected secret.
|
|
46
|
+
|
|
47
|
+
## Sync Usage
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from nui_shared_utils import create_snowflake_client
|
|
51
|
+
|
|
52
|
+
with create_snowflake_client(
|
|
53
|
+
secret_name="snowflake-agent-credentials",
|
|
54
|
+
role="NUI_LAMBDA",
|
|
55
|
+
warehouse="AGENT_WH",
|
|
56
|
+
database="NUI_MARKETS",
|
|
57
|
+
schema="ANALYTICS",
|
|
58
|
+
) as client:
|
|
59
|
+
rows = client.query(
|
|
60
|
+
"SELECT source_id, title FROM source_items WHERE source_id = ? LIMIT 10",
|
|
61
|
+
["edairynews"],
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The adapter applies NUI defaults for `role="NUI_LAMBDA"` and
|
|
66
|
+
`timezone="Pacific/Auckland"`. Override role, warehouse, database, schema, and
|
|
67
|
+
timezone explicitly for service accounts that need narrower production access.
|
|
68
|
+
|
|
69
|
+
## Async Usage
|
|
70
|
+
|
|
71
|
+
Use the async factory in FastAPI routes or other runtimes that already own an
|
|
72
|
+
event loop:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from contextlib import asynccontextmanager
|
|
76
|
+
from fastapi import FastAPI
|
|
77
|
+
from nui_shared_utils import create_async_snowflake_client
|
|
78
|
+
|
|
79
|
+
@asynccontextmanager
|
|
80
|
+
async def lifespan(app: FastAPI):
|
|
81
|
+
app.state.snowflake = create_async_snowflake_client(
|
|
82
|
+
secret_name="nui-analytics-snowflake",
|
|
83
|
+
role="NUI_ANALYTICS_API",
|
|
84
|
+
warehouse="ANALYTICS_WH",
|
|
85
|
+
database="NUI_MARKETS",
|
|
86
|
+
schema="ANALYTICS_API",
|
|
87
|
+
statement_timeout=30,
|
|
88
|
+
)
|
|
89
|
+
try:
|
|
90
|
+
yield
|
|
91
|
+
finally:
|
|
92
|
+
await app.state.snowflake.aclose()
|
|
93
|
+
|
|
94
|
+
app = FastAPI(lifespan=lifespan)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For Lambda handlers that use an async framework, create the client during
|
|
98
|
+
startup and close it during shutdown. For normal synchronous Lambda handlers,
|
|
99
|
+
prefer `create_snowflake_client`.
|
|
100
|
+
|
|
101
|
+
## Query Parameters
|
|
102
|
+
|
|
103
|
+
Snowflake SQL API bindings are positional `?` bindings:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
rows = client.query("SELECT id FROM trades WHERE solution_id = ?", [solution_id])
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Do not use connector-style `%s` placeholders or named `:name` bindings with this
|
|
110
|
+
adapter.
|
|
111
|
+
|
|
112
|
+
## Logging And Metrics Hooks
|
|
113
|
+
|
|
114
|
+
By default the adapter installs a redacting query logger. It logs SQL text and
|
|
115
|
+
bind parameter count, never bind values:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
client = create_snowflake_client(log_sql_max_chars=500)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For service metrics, pass a custom `on_query` hook:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
def record_query(sql, params):
|
|
125
|
+
metrics.put_metric("SnowflakeQuery", 1, "Count")
|
|
126
|
+
|
|
127
|
+
client = create_snowflake_client(on_query=record_query)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Keep hooks generic. Domain-specific table names, market-intelligence state
|
|
131
|
+
transitions, and analytics endpoint logic belong in the consuming service.
|
|
132
|
+
|
|
133
|
+
## Offline Tests
|
|
134
|
+
|
|
135
|
+
Use `snowflake_sql_api.testing.FakeSnowflake` to test service code without a
|
|
136
|
+
network or real Snowflake account:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import httpx
|
|
140
|
+
from snowflake_sql_api.testing import FakeSnowflake
|
|
141
|
+
from nui_shared_utils import create_async_snowflake_client
|
|
142
|
+
|
|
143
|
+
async def test_query(rsa_private_key_pem):
|
|
144
|
+
fake = FakeSnowflake()
|
|
145
|
+
fake.register("SELECT 1 AS value", [{"value": 1}])
|
|
146
|
+
|
|
147
|
+
async with httpx.AsyncClient(transport=fake.transport) as http_client:
|
|
148
|
+
client = create_async_snowflake_client(
|
|
149
|
+
account="xy12345.ap-southeast-2",
|
|
150
|
+
user="TEST_USER",
|
|
151
|
+
private_key=rsa_private_key_pem,
|
|
152
|
+
http_client=http_client,
|
|
153
|
+
log_queries=False,
|
|
154
|
+
)
|
|
155
|
+
assert await client.query_scalar("SELECT 1 AS value") == 1
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Add live smoke tests for behavior that FakeSnowflake cannot prove, such as
|
|
159
|
+
`MERGE` row counts, `PARSE_JSON(?)`, and timestamp/session timezone semantics.
|