nui-python-shared-utils 1.3.3__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.
Files changed (106) hide show
  1. nui_python_shared_utils-1.3.3/CLAUDE.md → nui_python_shared_utils-1.4.1/AGENTS.md +7 -4
  2. nui_python_shared_utils-1.4.1/CLAUDE.md +1 -0
  3. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/PKG-INFO +75 -2
  4. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/README.md +68 -1
  5. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/README.md +4 -0
  6. nui_python_shared_utils-1.4.1/docs/guides/llm-integration.md +200 -0
  7. nui_python_shared_utils-1.4.1/docs/guides/snowflake-integration.md +159 -0
  8. nui_python_shared_utils-1.4.1/nui_lambda_shared_utils/snowflake_client.py +3 -0
  9. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_python_shared_utils.egg-info/SOURCES.txt +8 -0
  10. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/__init__.py +18 -0
  11. nui_python_shared_utils-1.4.1/nui_shared_utils/llm.py +209 -0
  12. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/secrets_helper.py +5 -1
  13. nui_python_shared_utils-1.4.1/nui_shared_utils/snowflake_client.py +422 -0
  14. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/pyproject.toml +12 -1
  15. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/requirements-test.txt +7 -1
  16. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/setup.py +10 -0
  17. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_lazy_imports.py +19 -0
  18. nui_python_shared_utils-1.4.1/tests/test_llm.py +227 -0
  19. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_secrets_helper.py +25 -2
  20. nui_python_shared_utils-1.4.1/tests/test_snowflake_client.py +600 -0
  21. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/.editorconfig +0 -0
  22. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/.github/workflows/ci.yml +0 -0
  23. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/.github/workflows/publish.yml +0 -0
  24. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/.github/workflows/test.yml +0 -0
  25. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/.markdownlint-cli2.yaml +0 -0
  26. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/CONTRIBUTING.md +0 -0
  27. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/LICENSE +0 -0
  28. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/MANIFEST.in +0 -0
  29. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/development/testing.md +0 -0
  30. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/getting-started/configuration.md +0 -0
  31. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/getting-started/installation.md +0 -0
  32. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/getting-started/quickstart.md +0 -0
  33. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/guides/cli-tools.md +0 -0
  34. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/guides/elasticsearch-integration.md +0 -0
  35. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/guides/jwt-authentication.md +0 -0
  36. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/guides/lambda-utilities.md +0 -0
  37. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/guides/log-processing.md +0 -0
  38. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/guides/powertools-integration.md +0 -0
  39. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/guides/shared-types.md +0 -0
  40. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/docs/guides/slack-integration.md +0 -0
  41. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/mypy.ini +0 -0
  42. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/__init__.py +0 -0
  43. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/base_client.py +0 -0
  44. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/cli.py +0 -0
  45. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/cloudwatch_metrics.py +0 -0
  46. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/config.py +0 -0
  47. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/db_client.py +0 -0
  48. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/error_handler.py +0 -0
  49. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/es_client.py +0 -0
  50. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/es_query_builder.py +0 -0
  51. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/jwt_auth.py +0 -0
  52. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/lambda_helpers.py +0 -0
  53. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/log_processors.py +0 -0
  54. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/powertools_helpers.py +0 -0
  55. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/secrets_helper.py +0 -0
  56. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_client.py +0 -0
  57. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_formatter.py +0 -0
  58. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_setup/__init__.py +0 -0
  59. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_setup/channel_creator.py +0 -0
  60. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_setup/channel_definitions.py +0 -0
  61. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/slack_setup/setup_helpers.py +0 -0
  62. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/timezone.py +0 -0
  63. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_lambda_shared_utils/utils.py +0 -0
  64. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/base_client.py +0 -0
  65. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/cli.py +0 -0
  66. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/cloudwatch_metrics.py +0 -0
  67. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/config.py +0 -0
  68. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/db_client.py +0 -0
  69. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/error_handler.py +0 -0
  70. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/es_client.py +0 -0
  71. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/es_query_builder.py +0 -0
  72. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/jwt_auth.py +0 -0
  73. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/lambda_helpers.py +0 -0
  74. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/log_processors.py +0 -0
  75. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/powertools_helpers.py +0 -0
  76. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_client.py +0 -0
  77. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_formatter.py +0 -0
  78. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_setup/__init__.py +0 -0
  79. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_setup/channel_creator.py +0 -0
  80. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_setup/channel_definitions.py +0 -0
  81. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/slack_setup/setup_helpers.py +0 -0
  82. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/timezone.py +0 -0
  83. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/nui_shared_utils/utils.py +0 -0
  84. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/pytest.ini +0 -0
  85. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/redirect/README.md +0 -0
  86. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/redirect/pyproject.toml +0 -0
  87. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/redirect/setup.py +0 -0
  88. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/scripts/bench_imports.py +0 -0
  89. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/setup.cfg +0 -0
  90. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/__init__.py +0 -0
  91. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_aws_utils.py +0 -0
  92. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_base_client.py +0 -0
  93. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_cloudwatch_metrics.py +0 -0
  94. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_config.py +0 -0
  95. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_db_client.py +0 -0
  96. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_error_handler.py +0 -0
  97. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_es_client.py +0 -0
  98. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_es_query_builder.py +0 -0
  99. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_jwt_auth.py +0 -0
  100. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_lambda_helpers.py +0 -0
  101. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_log_processors.py +0 -0
  102. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_powertools_helpers.py +0 -0
  103. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_slack_client.py +0 -0
  104. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_slack_formatter.py +0 -0
  105. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_timezone.py +0 -0
  106. {nui_python_shared_utils-1.3.3 → nui_python_shared_utils-1.4.1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
- # CLAUDE.md
1
+ # AGENTS.md
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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
- - **[CLAUDE.md](CLAUDE.md)** (this file) - Development workflows, commands, testing strategies
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 Claude Code users**: The docs/ directory contains detailed usage patterns,
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.3
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] # All integrations
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] # All integrations
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.
@@ -0,0 +1,3 @@
1
+ """Backwards-compatibility shim. Use nui_shared_utils.snowflake_client instead."""
2
+
3
+ from nui_shared_utils.snowflake_client import * # noqa: F401,F403