Skip to content

python: encrypt to a live Postgres row #1389

Open

Description

@yujiyokoo

Created with Claude Code from the spec in #1341.

Parent

Sub-issue of #1341. Ticket 8 of 14.

What to build

The first live round trip, write side only:

rows = cipher.encrypt([User(email="a@example.com", age=3)])
cur.execute("INSERT INTO users (email, age) VALUES (%s, %s)", (rows[0]["email"], rows[0]["age"]))

encrypt returns a list of dicts, one per model, with a str EQL payload for each sealed field and the plaintext echoed for each Passthrough field; Ignore fields are absent. psycopg inserts the str with a plain %s into a public.eql_v4_text_eq column; no cast. The dicts are built in Rust from the engine's output tree; plaintext does not pass through a Python dict on the way in.

One engine client per process behind OnceLock with a multi-thread tokio runtime, AutoStrategy and the http feature; the default keyset loads when the client is first built. The GIL is released around the engine call. Request batching is the engine's: ceil(values / 500) requests per call.

Live test infrastructure arrives here: a Postgres container with the EQL v4 bundle, a pytest marker that skips without credentials, and the CI live job in tests-python.yml behind require-cs-secrets with its BINDING_EXEMPT_JOBS entry.

Acceptance criteria

  • The snippet above inserts a row that SELECT email FROM users returns as a non-null eql_v4_text_eq value.
  • encrypt([]) returns [] with no request.
  • A value whose Python type is not the field's exact type raises InvalidValueError with VALUE_WRONG_TYPE before any request (bool for an int field included).
  • 1001 one-field models make exactly three ZeroKMS requests (observed through the engine's request counter or a recording transport).
  • Two Cipher instances in one process share the engine client (observed through a process-wide counter the engine crate exposes for tests).
  • The live CI job runs on PRs from the repo, skips on forks, and the ffi-binding-step-order test accepts it.
  • A README stub in the Python workspace shows the DDL for the column and this snippet.

Metadata

Metadata

Assignees

No one assigned

    Labels

    SDKready-for-agentFully specified and ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions