ltcodecs 4.3.0__tar.gz → 4.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/PKG-INFO +1 -1
  2. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/__init__.py +1 -0
  3. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/dict_field_codec.py +9 -15
  4. ltcodecs-4.3.1/src/ltcodecs/metadata_field_codec.py +51 -0
  5. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/ros_msg_field_codec.py +10 -10
  6. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs.egg-info/PKG-INFO +1 -1
  7. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs.egg-info/SOURCES.txt +1 -0
  8. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs.egg-info/scm_file_list.json +41 -40
  9. ltcodecs-4.3.1/src/ltcodecs.egg-info/scm_version.json +8 -0
  10. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/tests/test_dict_codec.py +33 -0
  11. ltcodecs-4.3.0/src/ltcodecs.egg-info/scm_version.json +0 -8
  12. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/.gitignore +0 -0
  13. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/.gitlab/merge_request_templates/Default.md +0 -0
  14. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/.gitlab-ci.yml +0 -0
  15. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/CONTRIBUTING.md +0 -0
  16. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/LICENSE +0 -0
  17. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/README.md +0 -0
  18. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/Makefile +0 -0
  19. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/codec_configs.rst +0 -0
  20. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/conf.py +0 -0
  21. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/index.rst +0 -0
  22. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/quickstart.rst +0 -0
  23. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/reference/codec_aliases.rst +0 -0
  24. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/reference/codecs.rst +0 -0
  25. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/reference/config_schema.rst +0 -0
  26. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/reference/python_api.rst +0 -0
  27. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/requirements.txt +0 -0
  28. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/ros_messages.rst +0 -0
  29. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/docs/tests.rst +0 -0
  30. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/pyproject.toml +0 -0
  31. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/requirements.txt +0 -0
  32. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/setup.cfg +0 -0
  33. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/bool_codec.py +0 -0
  34. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/bytes_codec.py +0 -0
  35. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/ccl_latlon_bcd_codec.py +0 -0
  36. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/ccl_latlon_codec.py +0 -0
  37. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/dict_codec.py +0 -0
  38. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/exceptions.py +0 -0
  39. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/exponentialgolomb_integer_codec.py +0 -0
  40. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/field_codec.py +0 -0
  41. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/fixed_len_array_codec.py +0 -0
  42. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/fixedint_codec.py +0 -0
  43. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/float_codec.py +0 -0
  44. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/ieee_float_codec.py +0 -0
  45. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/linspace_float_codec.py +0 -0
  46. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/lzma_codec.py +0 -0
  47. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/multiple_field_codec.py +0 -0
  48. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/optional_field_codec.py +0 -0
  49. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/padding_codec.py +0 -0
  50. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/ros_message_codec.py +0 -0
  51. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/rostime_codec.py +0 -0
  52. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/string_codecs.py +0 -0
  53. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/string_enum_codec.py +0 -0
  54. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/variable_len_array_codec.py +0 -0
  55. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs/varint_codec.py +0 -0
  56. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs.egg-info/dependency_links.txt +0 -0
  57. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs.egg-info/requires.txt +0 -0
  58. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/src/ltcodecs.egg-info/top_level.txt +0 -0
  59. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/tests/__init__.py +0 -0
  60. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/tests/dict_codec.yaml +0 -0
  61. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/tests/string_msg_codec.yaml +0 -0
  62. {ltcodecs-4.3.0 → ltcodecs-4.3.1}/tests/test_codecs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ltcodecs
3
- Version: 4.3.0
3
+ Version: 4.3.1
4
4
  Summary: LT Codecs: Lightweight codecs for use over low-throughput links
5
5
  Author: Woods Hole Oceanographic Institution, Acoustic Communications Group
6
6
  License: LGPL-3.0-or-later
@@ -10,6 +10,7 @@ from .bool_codec import BoolCodec
10
10
  from .string_codecs import AsciiStringCodec
11
11
  from .string_enum_codec import StringEnumCodec
12
12
  from .bytes_codec import BytesCodec
13
+ from .metadata_field_codec import MetadataFieldCodec
13
14
  from .variable_len_array_codec import VariableLenArrayCodec
14
15
  from .fixed_len_array_codec import FixedLenArrayCodec
15
16
  from .dict_field_codec import DictFieldCodec
@@ -6,7 +6,6 @@ This module contains the field codec used for nested dictionaries.
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
- import operator
10
9
  from bitstring import BitArray, ConstBitStream
11
10
 
12
11
  from typing import Any, Dict
@@ -14,6 +13,7 @@ from typing import Any, Dict
14
13
  import ltcodecs as ltcodecs
15
14
 
16
15
  from .field_codec import FieldCodec
16
+ from .metadata_field_codec import MetadataFieldCodec
17
17
  from .multiple_field_codec import MultipleFieldCodec
18
18
  from .exceptions import EncodingFailed
19
19
 
@@ -49,7 +49,7 @@ class DictFieldCodec(FieldCodec):
49
49
  field_params["codec"]
50
50
  ](**field_params)
51
51
  else:
52
- self.field_codecs[field_name] = field_params["codec"]
52
+ self.field_codecs[field_name] = MetadataFieldCodec(field_params["codec"])
53
53
  except KeyError as err:
54
54
  raise KeyError(
55
55
  f"Error parsing codec config for {field_name}. Got params:\n{field_params}\nError: {err}"
@@ -75,7 +75,7 @@ class DictFieldCodec(FieldCodec):
75
75
  encoded_dict = {}
76
76
  for field_name, field_params in self.fields.items():
77
77
  field_codec = self.field_codecs.get(field_name, None)
78
- if not field_codec or isinstance(field_codec, str):
78
+ if not field_codec or isinstance(field_codec, MetadataFieldCodec):
79
79
  continue
80
80
  try:
81
81
  if isinstance(field_codec, MultipleFieldCodec):
@@ -110,18 +110,12 @@ class DictFieldCodec(FieldCodec):
110
110
  if not field_codec:
111
111
  continue
112
112
 
113
- if isinstance(field_codec, str):
113
+ if isinstance(field_codec, MetadataFieldCodec):
114
114
  if not metadata:
115
115
  continue
116
- if field_codec in ltcodecs.metadata_decoders:
117
- metadata_attribute = ltcodecs.metadata_decoders[field_codec]
118
- if isinstance(metadata, dict):
119
- value = metadata
120
- for attr_part in metadata_attribute.split("."):
121
- value = value[attr_part]
122
- else:
123
- value = operator.attrgetter(metadata_attribute)(metadata)
124
- decoded_message[field_name] = value
116
+ decoded_message[field_name] = field_codec.decode_metadata(
117
+ metadata, ltcodecs.metadata_decoders
118
+ )
125
119
  continue
126
120
 
127
121
  if isinstance(field_codec, MultipleFieldCodec):
@@ -150,9 +144,9 @@ class DictFieldCodec(FieldCodec):
150
144
  @property
151
145
  def min_length_bits(self) -> int:
152
146
  """Minimum encoded dictionary field length."""
153
- return sum([c.min_length_bits for c in self.field_codecs.values() if not isinstance(c, str)])
147
+ return sum([c.min_length_bits for c in self.field_codecs.values()])
154
148
 
155
149
  @property
156
150
  def max_length_bits(self) -> int:
157
151
  """Maximum encoded dictionary field length."""
158
- return sum([c.max_length_bits for c in self.field_codecs.values() if not isinstance(c, str)])
152
+ return sum([c.max_length_bits for c in self.field_codecs.values()])
@@ -0,0 +1,51 @@
1
+ """
2
+ ltcodecs.metadata_field_codec
3
+ -----------------------------
4
+
5
+ This module contains the zero-bit field codec used for metadata aliases.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import operator
11
+ from typing import Any, Dict
12
+
13
+ from bitstring import BitArray, ConstBitStream
14
+
15
+ from .field_codec import FieldCodec
16
+
17
+
18
+ class MetadataFieldCodec(FieldCodec):
19
+ """
20
+ Zero-bit codec for fields decoded from packet metadata.
21
+
22
+ Metadata fields do not consume payload bits, but they still need to behave
23
+ like field codecs so aggregate codec properties can handle them uniformly.
24
+ """
25
+
26
+ def __init__(self, codec_name: str, **kwargs) -> None:
27
+ super().__init__(**kwargs)
28
+ self.codec_name = codec_name
29
+
30
+ def encode(self, value: Any) -> tuple[BitArray, Any]:
31
+ return BitArray(), value
32
+
33
+ def decode(self, bits_to_decode: ConstBitStream) -> None:
34
+ return None
35
+
36
+ def decode_metadata(self, metadata: Any, metadata_decoders: Dict[str, str]) -> Any:
37
+ metadata_attribute = metadata_decoders[self.codec_name]
38
+ if isinstance(metadata, dict):
39
+ value = metadata
40
+ for attr_part in metadata_attribute.split("."):
41
+ value = value[attr_part]
42
+ return value
43
+ return operator.attrgetter(metadata_attribute)(metadata)
44
+
45
+ @property
46
+ def min_length_bits(self) -> int:
47
+ return 0
48
+
49
+ @property
50
+ def max_length_bits(self) -> int:
51
+ return 0
@@ -8,7 +8,6 @@ This module contains the field codec used for nested ROS messages.
8
8
  from __future__ import annotations
9
9
  import re
10
10
  from bitstring import ConstBitStream, BitArray
11
- import operator
12
11
 
13
12
  from typing import Union, Any, Dict
14
13
  import armw
@@ -16,6 +15,7 @@ import armw
16
15
  import ltcodecs as ltcodecs
17
16
 
18
17
  from .field_codec import FieldCodec
18
+ from .metadata_field_codec import MetadataFieldCodec
19
19
  from .multiple_field_codec import MultipleFieldCodec
20
20
  from .exceptions import EncodingFailed
21
21
 
@@ -186,8 +186,7 @@ class RosMsgFieldCodec(FieldCodec):
186
186
  field_params["codec"]
187
187
  ](**field_params)
188
188
  else:
189
- # The metadata codec is a string, which we use for a lookup later.
190
- self.field_codecs[field_name] = field_params["codec"]
189
+ self.field_codecs[field_name] = MetadataFieldCodec(field_params["codec"])
191
190
  except KeyError as err:
192
191
  raise KeyError(
193
192
  f"Error parsing codec config for {field_name}. Got params:\n{field_params}\nError: {err}"
@@ -224,7 +223,7 @@ class RosMsgFieldCodec(FieldCodec):
224
223
  for field_name, field_params in self.fields.items():
225
224
  field_codec = self.field_codecs.get(field_name, None)
226
225
  # Note that metadata encoding is done at the ros_msg_codec level, not here
227
- if not field_codec or isinstance(field_codec, str):
226
+ if not field_codec or isinstance(field_codec, MetadataFieldCodec):
228
227
  continue
229
228
  try:
230
229
  if isinstance(field_codec, MultipleFieldCodec):
@@ -262,15 +261,14 @@ class RosMsgFieldCodec(FieldCodec):
262
261
  continue
263
262
 
264
263
  # Handle metadata codecs
265
- if isinstance(field_codec, str):
264
+ if isinstance(field_codec, MetadataFieldCodec):
266
265
  if not metadata:
267
266
  continue
268
267
  try:
269
- if field_codec in ltcodecs.metadata_decoders:
270
- metadata_attribute = ltcodecs.metadata_decoders[field_codec]
271
- value = operator.attrgetter(metadata_attribute)(metadata)
272
- decoded_message[field_name] = value
273
- continue
268
+ decoded_message[field_name] = field_codec.decode_metadata(
269
+ metadata, ltcodecs.metadata_decoders
270
+ )
271
+ continue
274
272
  except KeyError:
275
273
  # if we don't recognize the metadata codec, just keep going
276
274
  continue
@@ -302,6 +300,8 @@ class RosMsgFieldCodec(FieldCodec):
302
300
  decoded_message = {}
303
301
  for field_name, field_params in self.fields.items():
304
302
  field_codec = self.field_codecs[field_name]
303
+ if isinstance(field_codec, MetadataFieldCodec):
304
+ continue
305
305
  if hasattr(field_codec, "decode_as_dict"):
306
306
  decoded_message[field_name] = field_codec.decode_as_dict(bits_to_decode)
307
307
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ltcodecs
3
- Version: 4.3.0
3
+ Version: 4.3.1
4
4
  Summary: LT Codecs: Lightweight codecs for use over low-throughput links
5
5
  Author: Woods Hole Oceanographic Institution, Acoustic Communications Group
6
6
  License: LGPL-3.0-or-later
@@ -34,6 +34,7 @@ src/ltcodecs/float_codec.py
34
34
  src/ltcodecs/ieee_float_codec.py
35
35
  src/ltcodecs/linspace_float_codec.py
36
36
  src/ltcodecs/lzma_codec.py
37
+ src/ltcodecs/metadata_field_codec.py
37
38
  src/ltcodecs/multiple_field_codec.py
38
39
  src/ltcodecs/optional_field_codec.py
39
40
  src/ltcodecs/padding_codec.py
@@ -1,55 +1,56 @@
1
1
  {
2
2
  "files": [
3
- "README.md",
4
- "LICENSE",
5
- "requirements.txt",
6
- "CONTRIBUTING.md",
7
- ".gitlab-ci.yml",
8
3
  ".gitignore",
4
+ ".gitlab-ci.yml",
5
+ ".gitlab/merge_request_templates/Default.md",
6
+ "CONTRIBUTING.md",
7
+ "LICENSE",
8
+ "README.md",
9
+ "docs/Makefile",
10
+ "docs/codec_configs.rst",
11
+ "docs/conf.py",
12
+ "docs/index.rst",
13
+ "docs/quickstart.rst",
14
+ "docs/reference/codec_aliases.rst",
15
+ "docs/reference/codecs.rst",
16
+ "docs/reference/config_schema.rst",
17
+ "docs/reference/python_api.rst",
18
+ "docs/requirements.txt",
19
+ "docs/ros_messages.rst",
20
+ "docs/tests.rst",
9
21
  "pyproject.toml",
10
- "src/ltcodecs/exponentialgolomb_integer_codec.py",
22
+ "requirements.txt",
23
+ "src/ltcodecs/__init__.py",
24
+ "src/ltcodecs/bool_codec.py",
25
+ "src/ltcodecs/bytes_codec.py",
26
+ "src/ltcodecs/ccl_latlon_bcd_codec.py",
27
+ "src/ltcodecs/ccl_latlon_codec.py",
11
28
  "src/ltcodecs/dict_codec.py",
29
+ "src/ltcodecs/dict_field_codec.py",
12
30
  "src/ltcodecs/exceptions.py",
31
+ "src/ltcodecs/exponentialgolomb_integer_codec.py",
13
32
  "src/ltcodecs/field_codec.py",
14
- "src/ltcodecs/ros_msg_field_codec.py",
15
- "src/ltcodecs/linspace_float_codec.py",
33
+ "src/ltcodecs/fixed_len_array_codec.py",
34
+ "src/ltcodecs/fixedint_codec.py",
16
35
  "src/ltcodecs/float_codec.py",
36
+ "src/ltcodecs/ieee_float_codec.py",
37
+ "src/ltcodecs/linspace_float_codec.py",
38
+ "src/ltcodecs/lzma_codec.py",
39
+ "src/ltcodecs/metadata_field_codec.py",
40
+ "src/ltcodecs/multiple_field_codec.py",
41
+ "src/ltcodecs/optional_field_codec.py",
42
+ "src/ltcodecs/padding_codec.py",
17
43
  "src/ltcodecs/ros_message_codec.py",
44
+ "src/ltcodecs/ros_msg_field_codec.py",
18
45
  "src/ltcodecs/rostime_codec.py",
19
- "src/ltcodecs/ccl_latlon_codec.py",
20
- "src/ltcodecs/bytes_codec.py",
21
- "src/ltcodecs/__init__.py",
22
- "src/ltcodecs/multiple_field_codec.py",
23
- "src/ltcodecs/dict_field_codec.py",
46
+ "src/ltcodecs/string_codecs.py",
24
47
  "src/ltcodecs/string_enum_codec.py",
25
- "src/ltcodecs/fixedint_codec.py",
26
- "src/ltcodecs/varint_codec.py",
27
- "src/ltcodecs/ccl_latlon_bcd_codec.py",
28
- "src/ltcodecs/lzma_codec.py",
29
- "src/ltcodecs/ieee_float_codec.py",
30
- "src/ltcodecs/fixed_len_array_codec.py",
31
48
  "src/ltcodecs/variable_len_array_codec.py",
32
- "src/ltcodecs/padding_codec.py",
33
- "src/ltcodecs/optional_field_codec.py",
34
- "src/ltcodecs/string_codecs.py",
35
- "src/ltcodecs/bool_codec.py",
36
- "docs/codec_configs.rst",
37
- "docs/conf.py",
38
- "docs/ros_messages.rst",
39
- "docs/requirements.txt",
40
- "docs/index.rst",
41
- "docs/quickstart.rst",
42
- "docs/Makefile",
43
- "docs/tests.rst",
44
- "docs/reference/codec_aliases.rst",
45
- "docs/reference/config_schema.rst",
46
- "docs/reference/python_api.rst",
47
- "docs/reference/codecs.rst",
48
- ".gitlab/merge_request_templates/Default.md",
49
- "tests/string_msg_codec.yaml",
50
- "tests/test_dict_codec.py",
51
- "tests/dict_codec.yaml",
49
+ "src/ltcodecs/varint_codec.py",
52
50
  "tests/__init__.py",
53
- "tests/test_codecs.py"
51
+ "tests/dict_codec.yaml",
52
+ "tests/string_msg_codec.yaml",
53
+ "tests/test_codecs.py",
54
+ "tests/test_dict_codec.py"
54
55
  ]
55
56
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "4.3.1",
3
+ "distance": 0,
4
+ "node": "gb95b17684296152051461fbb7c2cd01de1841f07",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-08-13"
8
+ }
@@ -102,3 +102,36 @@ def test_dict_codec_with_checksum() -> None:
102
102
  decoded_msg = codec.decode(bit_stream)
103
103
 
104
104
  assert decoded_msg == input_dict
105
+
106
+
107
+ def test_dict_codec_metadata_fields_are_zero_length_codecs() -> None:
108
+ """test metadata fields implement the field codec length properties"""
109
+ codec = ltcodecs.DictCodec(
110
+ {
111
+ "src": {"codec": "src"},
112
+ "count": {"codec": "integer", "min_value": 0, "max_value": 100},
113
+ }
114
+ )
115
+
116
+ count_codec = ltcodecs.VarintCodec(0, 100)
117
+
118
+ assert codec.min_length_bits == count_codec.min_length_bits
119
+ assert codec.max_length_bits == count_codec.max_length_bits
120
+ assert codec.root_field_codec.field_codecs["src"].min_length_bits == 0
121
+ assert codec.root_field_codec.field_codecs["src"].max_length_bits == 0
122
+
123
+
124
+ def test_dict_codec_decodes_metadata_field_from_dict() -> None:
125
+ """test metadata fields still decode from supplied packet metadata"""
126
+ codec = ltcodecs.DictCodec(
127
+ {
128
+ "src": {"codec": "src"},
129
+ "count": {"codec": "integer", "min_value": 0, "max_value": 100},
130
+ }
131
+ )
132
+
133
+ encoded_msg = codec.encode({"count": 21})
134
+ bit_stream = ConstBitStream(encoded_msg[0])
135
+ decoded_msg = codec.decode(bit_stream, received_packet={"packet": {"src": 3}})
136
+
137
+ assert decoded_msg == {"src": 3, "count": 21}
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "4.3.0",
3
- "distance": 0,
4
- "node": "g81a0abb84aa35b1e0a0526a5cb6981de880e2466",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-21"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes