Skip to content

Conversation

@mathewcohle
Copy link

The field is already exposed by NATS "$JS.API.STREAM.INFO.{name}" API: https://nats-io.github.io/nats.js/jetstream/types/StreamInfo.html

Since it's official part of protocol, let's expose it to the user. Golang implementation already do so: https://github.com/nats-io/nats.go/blob/4ec2f446e4cd829a7be3bf9aa16c43a7fddeaed9/jetstream/stream_config.go#L32

Example API response:

{
  "type": "io.nats.jetstream.api.v1.stream_info_response",
  "total": 0,
  "offset": 0,
  "limit": 0,
  "created": "2025-11-02T15:34:21.730004852Z",
  "config": {
    "name": "stream",
    "subjects": [
      "test.>"
    ],
    "retention": "limits",
    "max_consumers": -1,
    "max_msgs": -1,
    "max_bytes": 2147483648,
    "max_age": 86400000000000,
    "max_msgs_per_subject": -1,
    "max_msg_size": -1,
    "discard": "old",
    "storage": "file",
    "num_replicas": 1,
    "duplicate_window": 120000000000,
    "compression": "s2",
    "allow_direct": false,
    "mirror_direct": false,
    "sealed": false,
    "deny_delete": false,
    "deny_purge": false,
    "allow_rollup_hdrs": false,
    "consumer_limits": {}
  },
  "state": {
    "messages": 1249954,
    "bytes": 1036040689,
    "first_seq": 3326784,
    "first_ts": "2025-11-03T08:38:40.571255423Z",
    "last_seq": 4576737,
    "last_ts": "2025-11-03T13:34:30.52654449Z",
    "num_subjects": 2,
    "consumer_count": 1
  },
  "cluster": {
    "leader": "nats-0"
  },
  "ts": "2025-11-03T13:34:30.530396449Z"
}

@mathewcohle
Copy link
Author

mathewcohle commented Nov 3, 2025

Questions/comments:

  • not sure this should be part of changelog / if yes, please point me how to update it
  • looking around the codebase, I've used "defensive" pattern of marking the field optional and handle the case when it would not be part of API response. I believe the field is always present and thus can be parsed directly (dataclass field might be marked as datetime rather than Optional[datetime]). This would however require change of order of StreamInfo fields which might be potentially breaking change (if somebody depends on the order of fields). Let me know what is preferred way to do it
  • in case the argument for adding the field to StreamInfo seems weak, I can provide my use case why I would like to have (tl;dr stream:name + stream:created are poor man's stream ID)

@mathewcohle mathewcohle force-pushed the feat-add-created-to-stream-info branch from cd45ab3 to cd99131 Compare November 4, 2025 07:51
Copy link
Collaborator

@caspervonb caspervonb left a comment

Choose a reason for hiding this comment

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

lgtm

@caspervonb caspervonb requested a review from Copilot November 7, 2025 10:42
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

This PR adds support for the created timestamp field in JetStream StreamInfo objects. The field captures when a stream was created and is parsed from ISO format timestamps in API responses.

  • Added created field to StreamInfo dataclass as an optional datetime.datetime
  • Implemented ISO timestamp parsing in StreamInfo.from_response() method
  • Added test assertion to verify created field is populated correctly

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
nats/src/nats/js/api.py Added created field to StreamInfo and parsing logic to convert ISO timestamp to datetime
nats/tests/test_js.py Added assertion to verify the created field is correctly parsed as a datetime object

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

cls._convert(resp, "cluster", ClusterInfo)

if "created" in resp and resp["created"]:
resp["created"] = datetime.datetime.fromisoformat(resp["created"]).astimezone(datetime.timezone.utc)
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The timestamp parsing should use the _python38_iso_parsing helper method for consistency with other datetime fields in the codebase (e.g., leader_since in ClusterInfo and time in RawStreamMsg). This helper handles edge cases like 'Z' suffix replacement and trimming fractional seconds to microseconds. Consider changing to: resp['created'] = datetime.datetime.fromisoformat(cls._python38_iso_parsing(resp['created'])).astimezone(datetime.timezone.utc)

Suggested change
resp["created"] = datetime.datetime.fromisoformat(resp["created"]).astimezone(datetime.timezone.utc)
resp["created"] = datetime.datetime.fromisoformat(
cls._python38_iso_parsing(resp["created"])
).astimezone(datetime.timezone.utc)

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

@caspervonb shall I update?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes please 👍

Copy link
Author

Choose a reason for hiding this comment

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

Done - moved _python38_iso_parsing to Base from RawStreamMsg to not duplicate the code. Btw I think it maybe fixed a bug as ClusterInfo.from_response was using _python38_iso_parsing which is imho not accessible on main:
image
(even more concerning is imho the fact that no test/mypy check is failing; or I'm missing something :))

The field is already exposed by NATS "$JS.API.STREAM.INFO.{name}" API: https://nats-io.github.io/nats.js/jetstream/types/StreamInfo.html

Since it's official part of protocol, let's expose it to the user.
Golang implementation already do so: https://github.com/nats-io/nats.go/blob/4ec2f446e4cd829a7be3bf9aa16c43a7fddeaed9/jetstream/stream_config.go#L32

Example API response:
```json
{
  "type": "io.nats.jetstream.api.v1.stream_info_response",
  "total": 0,
  "offset": 0,
  "limit": 0,
  "created": "2025-11-02T15:34:21.730004852Z",
  "config": {
    "name": "stream",
    "subjects": [
      "test.>"
    ],
    "retention": "limits",
    "max_consumers": -1,
    "max_msgs": -1,
    "max_bytes": 2147483648,
    "max_age": 86400000000000,
    "max_msgs_per_subject": -1,
    "max_msg_size": -1,
    "discard": "old",
    "storage": "file",
    "num_replicas": 1,
    "duplicate_window": 120000000000,
    "compression": "s2",
    "allow_direct": false,
    "mirror_direct": false,
    "sealed": false,
    "deny_delete": false,
    "deny_purge": false,
    "allow_rollup_hdrs": false,
    "consumer_limits": {}
  },
  "state": {
    "messages": 1249954,
    "bytes": 1036040689,
    "first_seq": 3326784,
    "first_ts": "2025-11-03T08:38:40.571255423Z",
    "last_seq": 4576737,
    "last_ts": "2025-11-03T13:34:30.52654449Z",
    "num_subjects": 2,
    "consumer_count": 1
  },
  "cluster": {
    "leader": "nats-0"
  },
  "ts": "2025-11-03T13:34:30.530396449Z"
}
```
@mathewcohle mathewcohle force-pushed the feat-add-created-to-stream-info branch from cd99131 to a353232 Compare November 9, 2025 10:24
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