Skip to content

support subject_delete_marker_ttl in StreamConfig and limit_marker_ttl in KeyValueConfig#814

Open
adambudziak wants to merge 1 commit intonats-io:mainfrom
adambudziak:main
Open

support subject_delete_marker_ttl in StreamConfig and limit_marker_ttl in KeyValueConfig#814
adambudziak wants to merge 1 commit intonats-io:mainfrom
adambudziak:main

Conversation

@adambudziak
Copy link

closes

#725

I'm quite new in the repo so likely the change is not quite good enough to be merged.

wallyqs pushed a commit to wallyqs/nats.py that referenced this pull request Feb 2, 2026
Review of PR adding subject_delete_marker_ttl in StreamConfig and
limit_marker_ttl in KeyValueConfig for nats-server 2.11.0+ support.

Key findings:
- Magic strings should be extracted to constants
- KeyValueConfig.as_dict() should convert limit_marker_ttl to nanoseconds
- Core functionality is correct and follows existing patterns

https://claude.ai/code/session_01MteA8J2yH3ky5Y6coBpEAf
wallyqs pushed a commit to wallyqs/nats.py that referenced this pull request Feb 4, 2026
…w fixes

Implements PR nats-io#814 feature (closes nats-io#725) with improvements from code review:

- Add subject_delete_marker_ttl field to StreamConfig with nanosecond
  conversion in from_response/as_dict
- Add limit_marker_ttl field to KeyValueConfig, passed through to
  StreamConfig in create_key_value
- Add headers field to KeyValue.Entry for accessing message metadata
- Detect TTL-expired markers via Nats-Marker-Reason header in watcher

Review improvements applied:
- Extract "Nats-Marker-Reason" and "MaxAge" magic strings to module
  constants (NATS_MARKER_REASON, NATS_MARKER_MAX_AGE) in kv.py
- Add limit_marker_ttl nanosecond conversion in KeyValueConfig.as_dict()
  for consistency with ttl field
- Use Dict[str, str] type annotation for Entry.headers instead of bare dict
- Add server version comments on KeyValueConfig.limit_marker_ttl

https://claude.ai/code/session_01MteA8J2yH3ky5Y6coBpEAf
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds client-side support for JetStream delete-marker TTLs needed for per-key TTL semantics in KeyValue (i.e., ensuring watchers can observe delete/purge-style events when keys expire).

Changes:

  • Add subject_delete_marker_ttl to StreamConfig (including ns↔s conversion in serialization/deserialization).
  • Add limit_marker_ttl to KeyValueConfig and wire it into KV stream creation (subject_delete_marker_ttl).
  • Update KV watch handling to classify TTL-driven delete markers as DEL and surface message headers; add new tests covering both StreamConfig and KV behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

File Description
nats/tests/test_js.py Adds v2.11 feature tests for subject delete-marker TTL and KV per-key TTL delete marker behavior.
nats/src/nats/js/kv.py Enhances watcher parsing for TTL delete markers and exposes message headers on KV entries.
nats/src/nats/js/client.py Wires KeyValueConfig.limit_marker_ttl into KV stream creation via StreamConfig.subject_delete_marker_ttl.
nats/src/nats/js/api.py Adds new config fields and duration conversions for StreamConfig/KeyValueConfig.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +5138 to +5140
nc = NATS()
await nc.connect()

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Both new tests create/connect a NATS client but never close it. Please ensure await nc.close() runs even if assertions fail (e.g., via try/finally) to avoid leaking connections/tasks and causing later tests to flake.

Copilot uses AI. Check for mistakes.

js = nc.jetstream()
stream = await js.add_stream(
nats.js.api.StreamConfig(subject_delete_marker_ttl=1),
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This test publishes with msg_ttl=..., which requires the stream to have allow_msg_ttl=True (see existing test_publish_msg_ttl). Also, since this feature is 2.11+, add a server version guard/skip like other TTL-related tests to avoid failures on older servers.

Suggested change
nats.js.api.StreamConfig(subject_delete_marker_ttl=1),
nats.js.api.StreamConfig(subject_delete_marker_ttl=1, allow_msg_ttl=True),

Copilot uses AI. Check for mistakes.
Comment on lines +5159 to +5160
await asyncio.sleep(1.1)
assert len(messages) == 2
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Relying on asyncio.sleep(1.1) and then asserting an exact message count is timing-sensitive and can flake under load/slow CI. Prefer waiting until 2 messages are observed (e.g., with an asyncio.Event/Queue and asyncio.wait_for) instead of a fixed sleep.

Copilot uses AI. Check for mistakes.
Comment on lines +5171 to +5180
async def test_create_key_value_ttl(self):
nc = NATS()
await nc.connect()

js = nc.jetstream()

kv = await js.create_key_value(nats.js.api.KeyValueConfig("delete_marker_kv", ttl=1, limit_marker_ttl=1))

watcher = await kv.watchall()
await watcher.updates()
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This test creates a watcher (and a NATS connection) but never stops the watcher or closes the connection. Please call await watcher.stop() (or otherwise unsubscribe) and await nc.close() in a finally block to prevent background tasks from leaking into subsequent tests.

Copilot uses AI. Check for mistakes.
Comment on lines 84 to 90
value: Optional[bytes]
revision: Optional[int]
delta: Optional[int]
created: Optional[int]
operation: Optional[str]
headers: Optional[dict] = None

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

KeyValue.Entry.created is annotated as Optional[int], but the code assigns meta.timestamp (a datetime.datetime) and later treats it like a datetime (e.g., subtraction in purge_deletes). Since this hunk modifies Entry, update the type annotation to match actual usage; similarly, headers can be typed as Optional[Dict[str, str]] for consistency with Msg.headers.

Copilot uses AI. Check for mistakes.
Comment on lines +476 to +481
if msg.header:
if KV_OP in msg.header:
op = msg.header.get(KV_OP)
elif msg.header.get("Nats-Marker-Reason") == "MaxAge": # deleted by TTL
op = KV_DEL

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Watchers now translate TTL delete markers (Nats-Marker-Reason: MaxAge) into a KV DEL operation, but KeyValue.get()/_get() still only treats messages with KV-Operation headers as deleted/purged. To keep behavior consistent (and to avoid returning an empty value for an expired key), update the get-path deletion detection to also recognize TTL delete markers.

Copilot uses AI. Check for mistakes.
async def save_msg(msg):
messages.append(msg)

sub = await js.subscribe("delete.marker", cb=save_msg)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Variable sub is not used.

Suggested change
sub = await js.subscribe("delete.marker", cb=save_msg)
await js.subscribe("delete.marker", cb=save_msg)

Copilot uses AI. Check for mistakes.
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.

3 participants