Skip to content

feat: add spcs_pat for Snowflake SPCS gateway auth (DM-3656)#27

Open
oddy wants to merge 2 commits into
mainfrom
dm-3656-sfpat-from-ait-to-dmpython
Open

feat: add spcs_pat for Snowflake SPCS gateway auth (DM-3656)#27
oddy wants to merge 2 commits into
mainfrom
dm-3656-sfpat-from-ait-to-dmpython

Conversation

@oddy

@oddy oddy commented Jun 25, 2026

Copy link
Copy Markdown

What

Adds first-class support for authenticating to DataMasque instances hosted behind Snowflake SPCS (Snowpark Container Services) app ingress (*.snowflakecomputing.app).

  • New spcs_pat: Optional[str] field on DataMasqueInstanceConfig. When set, the client sends the Programmatic Access Token on the X-SF-SPCS-Authorization header so the Snowflake gateway lets the request through; the gateway strips it before forwarding, leaving DataMasque's own Authorization auth untouched. It is orthogonal to the existing password/token_source choice.
  • New datamasque/client/spcs.py: sets the header on the client's requests.Session (so it rides on every request, including the unauthenticated login) and registers a response hook that detects a gateway-originated 401/403 and raises a clear error.
  • New SpcsGatewayAuthError — deliberately outside the DataMasqueApiError subtree, so the client's 401 re-auth-and-retry path never loops on a gateway rejection. The message carries the Snowflake detail, request id, and a likely-cause hint.

Why

The AIT runner (datamasque-automation, DM-3656) carried this as a process-global monkeypatch of the module-level requests.request. The SDK now owns a per-client requests.Session, so the behaviour lives here cleanly as a session header + response hook — no global side effects, no host-scoping bookkeeping. The companion MR removes the hack from the runner.

Tests / docs

  • tests/test_spcs.py — header present on authenticated + login requests; no header when unset; gateway 401 raises SpcsGatewayAuthError and does not retry-loop; genuine DM 401 still retries; hint mapping; config coexistence with password/token_source.
  • Full suite (363) + ruff + mypy + sphinx-build -W all green.
  • Docs: usage.rst/README.rst SPCS section, client.rst autodoc, HISTORY.rst + version bump to 1.2.0.

Ticket: https://datamasque.atlassian.net/browse/DM-3656

Add `spcs_pat` to `DataMasqueInstanceConfig` for reaching DataMasque
instances hosted behind Snowflake SPCS (Snowpark Container Services) app
ingress (`*.snowflakecomputing.app`). When set, the client sends the
Programmatic Access Token on the `X-SF-SPCS-Authorization` header so the
Snowflake gateway lets the request through; the gateway strips it before
forwarding, leaving DataMasque's own auth untouched.

A response hook raises the new `SpcsGatewayAuthError` (deliberately outside
the `DataMasqueApiError` subtree, so the 401 re-auth retry never loops on a
gateway rejection) with the Snowflake detail and a likely-cause hint.

This replaces a process-global `requests.request` monkeypatch previously
carried in the AIT runner (DM-3656).
@oddy

oddy commented Jun 25, 2026

Copy link
Copy Markdown
Author

Companion MR that removes the AIT-runner monkeypatch and consumes this feature: https://git.datamasque.com/datamasque/datamasque-automation/-/merge_requests/1045

Merge this PR first; the runner pins datamasque-python to commit 6e973bf.

@oddy oddy requested a review from cph-datamasque June 25, 2026 23:50
@oddy oddy self-assigned this Jun 25, 2026
DataMasque running inside Snowflake SPCS saves connections with
`snowflake_stage_location=spcs`, a value the client's enum did not model.
Because `create_or_update_connection` lists and deserialises every
connection to find a match, a single SPCS-staged Snowflake connection on a
shared instance made the client raise `ValidationError` during setup of any
other connection.

Add `spcs` to `SnowflakeStageLocation` (no associated storage fields; the
container stages on its own storage). Server-side validation already covers
the per-stage required fields, so no client-side validator change is needed.

Lets ui-testing retract the create_connection workaround in MR !185 (DM-3656).
@oddy

oddy commented Jun 26, 2026

Copy link
Copy Markdown
Author

Follow-up commit cc71074: added spcs to SnowflakeStageLocation.

This is the durable fix for the failure mode that ui-testing MR !185 works around. When DataMasque runs inside Snowflake SPCS it saves connections with snowflake_stage_location=spcs; the client enum didn't model it, so listing connections on a shared instance that held one raised ValidationError and broke create_or_update_connection setup for unrelated tests. Once this PR lands (and the consuming projects bump to it), that workaround can be retracted.

Regression test: test_snowflake_connection_model_validate_with_spcs_stage_location.

@cph-datamasque cph-datamasque left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Needs a rebase.

Please ask your claude to write in sembr and to use our house style for code references (single-backticks, not double).

I don't think it's necessary to include the implementation details of how DM and Snowflake handle the auth header. A user of this library wouldn't really care - debugging anything in that scope is out of their hands, really.

Actual code impl is fine.

Suggest 1.1.3 for the version (1.1.2 is the latest), though if you wanted to go to 1.2.0 I've no strong objections.

The client calls `token_source` on each authentication attempt,
so the callable is free to fetch and refresh tokens out-of-band (e.g. from a secrets manager).

`spcs_pat` is an optional Snowflake Programmatic Access Token for reaching a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

tell your claude to look at CONTRIBUTING.rst, where you'll see guidelines for semantic breaking.

Do we need to talk about the implementation details?

verify_ssl: bool = True
token_source: Optional[Callable[[], str]] = None
spcs_pat: Optional[str] = None
"""Snowflake Programmatic Access Token for a DataMasque instance hosted behind

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

D213 and sembr. Ruff should have yelled at you.

Same thoughts re implementation details may be unnecessary.

Mint is casual language - perhaps Create


class SpcsGatewayAuthError(DataMasqueException):
"""
Raised when a Snowflake SPCS app gateway rejects the configured ``spcs_pat``.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sembr.

Good comment explaining why this doesn't inherit from DataMasqueApiError.

Comment thread datamasque/client/spcs.py
@@ -0,0 +1,176 @@
"""
Snowflake SPCS app gateway authentication for :class:`DataMasqueClient`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Guess what two things I'm going to say again.

Comment thread datamasque/client/spcs.py
"""
True if at least one header-level Snowflake gateway marker is present.

Looks for either ``Server: _`` (the gateway's literal Server header value)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

sembr again... and the comment

I'm not going to comment any more individual instances, have your claude fix them all up please

Comment thread pyproject.toml
[project]
name = "datamasque-python"
version = "1.1.2"
version = "1.2.0"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

use 1.1.3

Comment thread README.rst
raised by ``start_masking_run`` when the server rejects the run.
- ``DataMasqueUserError`` —
raised by user-management methods when the input is invalid.
- ``SpcsGatewayAuthError`` —

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not to say the snowflake stuff isn't valuable, but is this "worthy" (mainly, frequently-used) enough of being included in top level README?

Comment thread README.rst
For a DataMasque instance hosted behind Snowflake SPCS (Snowpark Container Services) app ingress
(a ``*.snowflakecomputing.app`` ``base_url``),
pass a Snowflake Programmatic Access Token as ``spcs_pat`` on ``DataMasqueInstanceConfig``;
the client sends it on the ``X-SF-SPCS-Authorization`` header to clear the Snowflake gateway,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

more implementation detail that doesn't need to be in the README, if even present at all

Comment thread docs/usage.rst
client = DataMasqueClient(config)
client.authenticate()

Mint the PAT in Snowsight (User profile → Programmatic access tokens) for an

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

s/Mint/Create/

Comment thread HISTORY.rst
Comment on lines +8 to +19
* Added ``spcs_pat`` to ``DataMasqueInstanceConfig`` for authenticating to
DataMasque instances hosted behind Snowflake SPCS (Snowpark Container Services)
app ingress. When set, the client sends the Programmatic Access Token on the
``X-SF-SPCS-Authorization`` header to clear the Snowflake gateway, independently
of the instance's own DataMasque auth.
* Added ``SpcsGatewayAuthError``, raised when the SPCS gateway rejects the PAT
before the request reaches DataMasque (with the Snowflake detail and a hint at
the likely cause).
* Added ``spcs`` to ``SnowflakeStageLocation`` so connections staged inside
Snowflake SPCS deserialise correctly. Previously, listing connections on an
instance that held an SPCS-staged Snowflake connection raised a
``ValidationError`` on the unknown stage value.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Follow existing style - nowhere near as verbose. And, you guessed it, sembr.

Suggested change
* Added ``spcs_pat`` to ``DataMasqueInstanceConfig`` for authenticating to
DataMasque instances hosted behind Snowflake SPCS (Snowpark Container Services)
app ingress. When set, the client sends the Programmatic Access Token on the
``X-SF-SPCS-Authorization`` header to clear the Snowflake gateway, independently
of the instance's own DataMasque auth.
* Added ``SpcsGatewayAuthError``, raised when the SPCS gateway rejects the PAT
before the request reaches DataMasque (with the Snowflake detail and a hint at
the likely cause).
* Added ``spcs`` to ``SnowflakeStageLocation`` so connections staged inside
Snowflake SPCS deserialise correctly. Previously, listing connections on an
instance that held an SPCS-staged Snowflake connection raised a
``ValidationError`` on the unknown stage value.
* Added ``spcs_pat`` to ``DataMasqueInstanceConfig`` for authenticating to DataMasque instances hosted on Snowpark Container Services.
* Added ``SpcsGatewayAuthError``, raised when the SPCS gateway rejects the PAT.
* Added ``spcs`` option to ``SnowflakeStageLocation``.

Could also structure as a "Added support for DataMasque deployments on Snowpark Container Services (SPCS)" heading with the three bullets nested below it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants