Skip to content

tdfs4ds.utils.reconnect

tdfs4ds.utils.reconnect

Teradata reconnection helper.

Re-establishes the teradataml context after the connection is lost (idle timeout, network blip, server restart). Credentials are read from the same TDFS4DS_TD_* environment variables / .env used by the server bootstrap:

TDFS4DS_TD_HOST       Teradata host (required)
TDFS4DS_TD_USERNAME   Teradata user (required for user/password auth)
TDFS4DS_TD_PASSWORD   Teradata password (required for user/password auth)
TDFS4DS_TD_LOGDATA    token payload, e.g. 'token=<jwt>' (alternative to
                      username/password; implies LOGMECH=JWT by default)
TDFS4DS_TD_LOGMECH    logon mechanism (optional)
TDFS4DS_TD_DATABASE   default database (optional)
TDFS4DS_TD_ENCRYPT    wire encryption — 'true' or 'false' (optional)
Used two ways
  • manually — tdfs4ds.reconnect_teradata();
  • automatically — execute_query detects a dropped-connection error, calls :func:reconnect_teradata once, and retries the query.

logger module-attribute

logger = getLogger('tdfs4ds')

_SERVER_RESPONSE_RE module-attribute

_SERVER_RESPONSE_RE = compile('\\[teradata database\\]')

_CONNECTION_ERROR_MARKERS module-attribute

_CONNECTION_ERROR_MARKERS = ('socket', 'broken pipe', 'connection reset', 'econnreset', 'connection refused', 'econnrefused', 'connection aborted', 'connection closed', 'connection is closed', 'closed the connection', 'server closed', 'forcibly closed', 'not connected', 'no longer connected', 'failure sending request', 'failure receiving response', 'no more sessions', 'session has been aborted', 'connection timed out', 'connect timeout', 'sqlstate 08s01', 'sqlstate 08003')

_PASSWORD_MECHANISMS module-attribute

_PASSWORD_MECHANISMS = ('TD2', 'LDAP')

_CREDENTIAL_REFUSAL_MARKERS module-attribute

_CREDENTIAL_REFUSAL_MARKERS = ('[error 544]', 'requires username and password', '[error 8017]', 'sqlstate 28000', 'invalid authorization')

_TRUE_WORDS module-attribute

_TRUE_WORDS = {'true', '1', 'yes', 'y', 'on'}

_FALSE_WORDS module-attribute

_FALSE_WORDS = {'false', '0', 'no', 'n', 'off'}

_env

_env(name, default=None)

_chained_text

_chained_text(exc, limit=8)

Every message along exc's __cause__ chain, lower-cased and joined.

teradataml wraps the driver error rather than replacing it, so the words that identify the fault usually live in __cause__: TDML_2000 Failed to connect to Teradata Vantage. says nothing on its own.

__context__ is deliberately NOT followed, which is the one difference from :func:_describe_exception. __context__ is whatever exception happened to be in flight when this one was raised — frequently unrelated, since the package issues cleanup statements from inside except blocks — and letting that text into a classification makes two independent failures contaminate each other. A human reading a log wants everything; a decision wants only what was explicitly chained with raise ... from.

Cycle-safe and bounded; never raises.

is_connection_error

is_connection_error(exc: BaseException) -> bool

True when exc means the connection is gone — not that a statement failed.

The distinction matters because the only automatic caller acts on it by tearing the session down (execute_query → :func:reconnect_teradata). A false positive therefore destroys a healthy context, and with it every volatile table and volatile ART the session holds, in response to an error that had nothing to do with connectivity.

So the test is deliberately asymmetric: a server response anywhere in the chain proves the connection was alive and vetoes the classification before any positive marker is considered. A missed reconnect merely surfaces the real error to the caller, which is recoverable; a spurious one is not.

The cost of the veto is bounded and accepted: if the database answers a statement and then the session dies (a DBA abort, say), the next statement fails driver-side with a transport error and reconnects correctly — one extra surfaced error, no lost context.

encrypt_setting

encrypt_setting(value, source: str = 'TDFS4DS_TD_ENCRYPT')

TDFS4DS_TD_ENCRYPT (or the Connect form's field) as a Python bool, or None.

The value must reach tdml.create_context as a bool: teradataml upper-cases every string connection parameter (for TMODE), so encryptdata='false' reached the driver as "FALSE", which teradatasql 20.0.69 refuses ("[Error 169] Unable to parse JSON connection parameters ... trying to unmarshal "FALSE" into bool"); only a bool is lower-cased. The raw driver wants the lower-case string instead (:func:_driver_kwargs). None / '' = not set. Anything not a yes/no word raises ValueError naming source.

_connect_kwargs

_connect_kwargs() -> dict | None

Build create_context kwargs from TDFS4DS_TD_*; None if incomplete.

Two credential shapes are accepted, because the platforms differ:

  • user/password — HOST + USERNAME + PASSWORD, optionally with LOGMECH. The historical shape.
  • token — HOST + LOGDATA (+ LOGMECH, normally 'JWT'), which is how a Teradata session is opened in an environment that authenticates its users with a platform token rather than a password: create_context(host=…, logmech='JWT', logdata='token=…'). There is no username or password to supply, so requiring them here made a JWT deployment inexpressible — a long-lived agent could open a context by hand but could never recover one, since every automatic reconnect funnels through this function.

LOGDATA is a credential (it carries the token), so it is redacted by export_config like any other and never logged.

_driver_kwargs

_driver_kwargs(kwargs)

Map create_context kwargs onto raw teradatasql.connect kwargs.

teradatasql takes every parameter as a string: a bool becomes 'true'/'false'.

_credentials_probe

_credentials_probe(kwargs)

Can these credentials open a session? (True | False | None, detail).

False only when the logon was refused — a malformed credential set, or a rejected one. None means inconclusive (unreachable host, no driver), which is the normal state of affairs during a real outage and must never be read as a refusal.

Raw teradatasql, never tdml.create_context: teradataml holds ONE process-wide context and assigns its global engine before testing it, so a failed create_context is not a question — it is an answer that has already broken the caller's session. Same constraint, and the same solution, as :func:jwt_logon_verdict and the benchmark module.

This adds no failed logon attempt over the previous behaviour: on a refusal it replaces the create_context that used to fail, rather than preceding it.

jwt_logon_verdict

jwt_logon_verdict(live: bool = True)

Does the database accept TDFS4DS_TD_LOGDATA as a JWT logon?

Returns (True | False | None, detail) — None when it could not be established, which is not a failure.

This exists because a platform can hand out two tokens that look alike and are not interchangeable: a session/SSO token, which authenticates against the platform's own services, and one minted for a Vantage logon. Only the second is a database credential. The first fails with the database's own Error 8017 / SQLState 28000 — never an HTTP 401 — everywhere it is used as one, including inside a gateway that opens a session on the caller's behalf, which is why such a failure surfaces far from the setting that caused it. Confirmed live on AI Studio.

Uses raw teradatasql, never tdml.create_context: teradataml holds ONE process-wide context, so opening one here would replace the caller's working connection as a side effect of a check. Same reason the benchmark module talks to the driver directly.

The token is read from the named TDFS4DS_TD_LOGDATA. The platform's unprefixed OAUTH_ACCESS_TOKEN is deliberately NOT read (v0.3.1.154): a generic name can be owned by an unrelated component, and forwarding some other component's credential to the database is exactly the mistake that rule prevents.

_describe_exception

_describe_exception(exc, limit=4, width=300)

Render exc with its chained causes, not just its first line.

TDML_2000 Failed to connect to Teradata Vantage. says nothing about WHY, and teradataml wraps the driver error rather than replacing it - so the actionable half lives in __cause__: a DNS miss, a refused connection and a rejected logon are three different faults with three different fixes, and they all render identically at the top level.

Logging only str(exc).splitlines()[0] discarded exactly that. A live AgentOps pod failed here in 87 ms - far too fast for a TCP timeout, so a name that did not resolve - and the log was indistinguishable from an unreachable host or a bad password.

Cycle-safe and bounded; used only for logging, so it never raises.

reconnect_teradata

reconnect_teradata(raise_error: bool = False) -> bool

Re-establish the teradataml context from TDFS4DS_TD_* credentials.

Returns True on success, False on failure (or raises when raise_error=True). A no-op-safe call: if the credentials are not configured it logs a warning and returns False.

It refuses to destroy a working context on the way to a broken one. The credentials are probed with the raw driver first, and a refusal leaves the existing context untouched. This is not hypothetical tidiness: create_context calls remove_context internally and assigns teradataml's global engine before testing it, so a failed reconnect used to leave get_context() returning a poisoned engine — every later statement in the process then failed with that logon error instead of its own, and the session could not self-heal. That is how one nominal "table already exists" during connect() ended a notebook's session.