flow.record 3.22.dev8__tar.gz → 3.22.dev9__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 (97) hide show
  1. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/PKG-INFO +1 -1
  2. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/sqlite.py +3 -3
  3. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/base.py +17 -17
  4. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/packer.py +1 -1
  5. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/selector.py +1 -1
  6. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/version.py +3 -3
  7. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow.record.egg-info/PKG-INFO +1 -1
  8. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_sqlite_duckdb.py +37 -1
  9. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/record/test_record.py +10 -10
  10. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/.git-blame-ignore-revs +0 -0
  11. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/.gitattributes +0 -0
  12. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/COPYRIGHT +0 -0
  13. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/LICENSE +0 -0
  14. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/MANIFEST.in +0 -0
  15. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/README.md +0 -0
  16. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/examples/__init__.py +0 -0
  17. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/examples/filesystem.py +0 -0
  18. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/examples/passivedns.py +0 -0
  19. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/examples/records.json +0 -0
  20. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/examples/selectors.py +0 -0
  21. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/examples/tcpconn.py +0 -0
  22. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/__init__.py +0 -0
  23. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/__init__.py +0 -0
  24. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/archive.py +0 -0
  25. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/avro.py +0 -0
  26. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/broker.py +0 -0
  27. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/csvfile.py +0 -0
  28. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/duckdb.py +0 -0
  29. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/elastic.py +0 -0
  30. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/jsonfile.py +0 -0
  31. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/line.py +0 -0
  32. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/mongo.py +0 -0
  33. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/split.py +0 -0
  34. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/splunk.py +0 -0
  35. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/stream.py +0 -0
  36. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/text.py +0 -0
  37. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/adapter/xlsx.py +0 -0
  38. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/context.py +0 -0
  39. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/exceptions.py +0 -0
  40. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/fieldtypes/__init__.py +0 -0
  41. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/fieldtypes/credential.py +0 -0
  42. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/__init__.py +0 -0
  43. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/ip.py +0 -0
  44. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/ipv4.py +0 -0
  45. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/tcp.py +0 -0
  46. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/udp.py +0 -0
  47. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/jsonpacker.py +0 -0
  48. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/stream.py +0 -0
  49. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/tools/__init__.py +0 -0
  50. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/tools/geoip.py +0 -0
  51. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/tools/rdump.py +0 -0
  52. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/utils.py +0 -0
  53. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow/record/whitelist.py +0 -0
  54. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow.record.egg-info/SOURCES.txt +0 -0
  55. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow.record.egg-info/dependency_links.txt +0 -0
  56. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow.record.egg-info/entry_points.txt +0 -0
  57. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow.record.egg-info/requires.txt +0 -0
  58. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/flow.record.egg-info/top_level.txt +0 -0
  59. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/pyproject.toml +0 -0
  60. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/setup.cfg +0 -0
  61. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/__init__.py +0 -0
  62. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/_data/.gitkeep +0 -0
  63. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/_docs/Makefile +0 -0
  64. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/_docs/conf.py +0 -0
  65. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/_docs/index.rst +0 -0
  66. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/_utils.py +0 -0
  67. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/__init__.py +0 -0
  68. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_avro.py +0 -0
  69. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_csv.py +0 -0
  70. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_elastic.py +0 -0
  71. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_json.py +0 -0
  72. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_line.py +0 -0
  73. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_splunk.py +0 -0
  74. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_text.py +0 -0
  75. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/adapter/test_xlsx.py +0 -0
  76. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/conftest.py +0 -0
  77. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/fieldtypes/__init__.py +0 -0
  78. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/fieldtypes/test_boolean.py +0 -0
  79. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/fieldtypes/test_fieldtypes.py +0 -0
  80. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/fieldtypes/test_ip.py +0 -0
  81. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/packer/__init__.py +0 -0
  82. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/packer/test_json_packer.py +0 -0
  83. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/packer/test_packer.py +0 -0
  84. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/record/__init__.py +0 -0
  85. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/record/test_adapter.py +0 -0
  86. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/record/test_context.py +0 -0
  87. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/record/test_descriptor.py +0 -0
  88. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/record/test_multi_timestamp.py +0 -0
  89. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/selector/__init__.py +0 -0
  90. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/selector/test_compiled.py +0 -0
  91. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/selector/test_selectors.py +0 -0
  92. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/test_deprecations.py +0 -0
  93. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/test_regressions.py +0 -0
  94. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/test_utils.py +0 -0
  95. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/tools/__init__.py +0 -0
  96. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tests/tools/test_rdump.py +0 -0
  97. {flow_record-3.22.dev8 → flow_record-3.22.dev9}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flow.record
3
- Version: 3.22.dev8
3
+ Version: 3.22.dev9
4
4
  Summary: A library for defining and creating structured data (called records) that can be streamed to disk or piped to other tools that use flow.record
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -109,10 +109,10 @@ def prepare_insert_sql(table_name: str, field_names: tuple[str]) -> str:
109
109
 
110
110
  def db_insert_record(con: sqlite3.Connection, record: Record) -> None:
111
111
  """Insert a record into the database."""
112
- table_name = record._desc.name
112
+ descriptor = record._desc
113
+ table_name = descriptor.name
113
114
  rdict = record._asdict()
114
-
115
- sql = prepare_insert_sql(table_name, record.__slots__)
115
+ sql = prepare_insert_sql(table_name, tuple(rdict.keys()))
116
116
 
117
117
  # Convert values to str() for types we don't support
118
118
  values = []
@@ -247,22 +247,22 @@ class GroupedRecord(Record):
247
247
 
248
248
  def __init__(self, name: str, records: list[Record | GroupedRecord]):
249
249
  super().__init__()
250
- self.name = to_str(name)
251
- self.records = []
252
- self.descriptors = []
253
- self.flat_fields = []
250
+ self.__name__ = to_str(name)
251
+ self.__records__ = []
252
+ self.__descriptors__ = []
253
+ self.__flat_fields__ = []
254
254
 
255
255
  # to avoid recursion in __setattr__ and __getattr__
256
256
  self.__dict__["fieldname_to_record"] = OrderedDict()
257
257
 
258
258
  for rec in records:
259
259
  if isinstance(rec, GroupedRecord):
260
- for r in rec.records:
261
- self.records.append(r)
262
- self.descriptors.append(r._desc)
260
+ for r in rec.__records__:
261
+ self.__records__.append(r)
262
+ self.__descriptors__.append(r._desc)
263
263
  else:
264
- self.records.append(rec)
265
- self.descriptors.append(rec._desc)
264
+ self.__records__.append(rec)
265
+ self.__descriptors__.append(rec._desc)
266
266
 
267
267
  all_fields = rec._desc.get_all_fields()
268
268
  required_fields = rec._desc.get_required_fields()
@@ -272,10 +272,10 @@ class GroupedRecord(Record):
272
272
  continue
273
273
  self.fieldname_to_record[fname] = rec
274
274
  if fname not in required_fields:
275
- self.flat_fields.append(field)
275
+ self.__flat_fields__.append(field)
276
276
  # Flat descriptor to maintain compatibility with Record
277
277
 
278
- self._desc = RecordDescriptor(self.name, [(f.typename, f.name) for f in self.flat_fields])
278
+ self._desc = RecordDescriptor(self.__name__, [(f.typename, f.name) for f in self.__flat_fields__])
279
279
 
280
280
  # _field_types to maintain compatibility with RecordDescriptor
281
281
  self._field_types = self._desc.recordType._field_types
@@ -291,7 +291,7 @@ class GroupedRecord(Record):
291
291
  None or the record
292
292
 
293
293
  """
294
- for record in self.records:
294
+ for record in self.__records__:
295
295
  if record._desc.name == type_name:
296
296
  return record
297
297
  return None
@@ -304,7 +304,7 @@ class GroupedRecord(Record):
304
304
  return OrderedDict((k, getattr(self, k)) for k in keys if k not in exclude)
305
305
 
306
306
  def __repr__(self) -> str:
307
- return f"<{self.name} {self.records}>"
307
+ return f"<{self.__name__} {self.__records__}>"
308
308
 
309
309
  def __setattr__(self, attr: str, val: Any) -> None:
310
310
  if attr in getattr(self, "fieldname_to_record", {}):
@@ -320,18 +320,18 @@ class GroupedRecord(Record):
320
320
 
321
321
  def _pack(self) -> tuple[str, tuple]:
322
322
  return (
323
- self.name,
324
- tuple(record._pack() for record in self.records),
323
+ self.__name__,
324
+ tuple(record._pack() for record in self.__records__),
325
325
  )
326
326
 
327
327
  def _replace(self, **kwds) -> GroupedRecord:
328
328
  new_records = [
329
329
  record.__class__(*map(kwds.pop, record.__slots__, (getattr(self, k) for k in record.__slots__)))
330
- for record in self.records
330
+ for record in self.__records__
331
331
  ]
332
332
  if kwds:
333
333
  raise ValueError(f"Got unexpected field names: {list(kwds)!r}")
334
- return GroupedRecord(self.name, new_records)
334
+ return GroupedRecord(self.__name__, new_records)
335
335
 
336
336
 
337
337
  def is_valid_field_name(name: str, check_reserved: bool = True) -> bool:
@@ -78,7 +78,7 @@ class RecordPacker:
78
78
  packed = RECORD_PACK_TYPE_VARINT, (neg, v.to_bytes((v.bit_length() + 7) // 8, "big"))
79
79
 
80
80
  elif isinstance(obj, GroupedRecord):
81
- for desc in obj.descriptors:
81
+ for desc in obj.__descriptors__:
82
82
  if desc.identifier not in self.descriptors:
83
83
  self.register(desc, True)
84
84
 
@@ -115,7 +115,7 @@ def upper(s: str | Any) -> str | Any:
115
115
  def names(r: Record | WrappedRecord | GroupedRecord) -> set[str]:
116
116
  """Return the available names as a set in the Record otherwise ['UnknownRecord']."""
117
117
  if isinstance(r, GroupedRecord):
118
- return {sub_record._desc.name for sub_record in r.records}
118
+ return {sub_record._desc.name for sub_record in r.__records__}
119
119
  if isinstance(r, (Record, WrappedRecord)):
120
120
  return {r._desc.name}
121
121
  return ["UnknownRecord"]
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '3.22.dev8'
32
- __version_tuple__ = version_tuple = (3, 22, 'dev8')
31
+ __version__ = version = '3.22.dev9'
32
+ __version_tuple__ = version_tuple = (3, 22, 'dev9')
33
33
 
34
- __commit_id__ = commit_id = 'g1ab6b5481'
34
+ __commit_id__ = commit_id = 'ga30f10d5d'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flow.record
3
- Version: 3.22.dev8
3
+ Version: 3.22.dev9
4
4
  Summary: A library for defining and creating structured data (called records) that can be streamed to disk or piped to other tools that use flow.record
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import hashlib
3
4
  import sqlite3
4
5
  from contextlib import closing
5
6
  from datetime import datetime, timezone
@@ -12,7 +13,7 @@ except ModuleNotFoundError:
12
13
 
13
14
  import pytest
14
15
 
15
- from flow.record import Record, RecordDescriptor, RecordReader, RecordWriter
16
+ from flow.record import GroupedRecord, Record, RecordDescriptor, RecordReader, RecordWriter
16
17
  from flow.record.adapter.sqlite import prepare_insert_sql
17
18
  from flow.record.base import normalize_fieldname
18
19
  from flow.record.exceptions import RecordDescriptorError
@@ -400,3 +401,38 @@ def test_selector(tmp_path: Path, db: Database) -> None:
400
401
  with RecordReader(f"{db.scheme}://{db_path}", selector="r.name == 'record12345'") as reader:
401
402
  records = list(reader)
402
403
  assert len(records) == 0
404
+
405
+
406
+ @sqlite_duckdb_parametrize
407
+ def test_grouped_record(tmp_path: Path, db: Database) -> None:
408
+ """Test adapter with grouped records."""
409
+ db_path = tmp_path / "records.db"
410
+
411
+ DigestRecord = RecordDescriptor(
412
+ "meta/record",
413
+ [
414
+ ("digest", "digest"),
415
+ ],
416
+ )
417
+
418
+ with RecordWriter(f"{db.scheme}://{db_path}") as writer:
419
+ for record in generate_records(10):
420
+ digest_record = DigestRecord(
421
+ digest=(
422
+ hashlib.md5(record.name.encode()).hexdigest(),
423
+ hashlib.sha1(record.name.encode()).hexdigest(),
424
+ hashlib.sha256(record.name.encode()).hexdigest(),
425
+ )
426
+ )
427
+ grouped = GroupedRecord("grouped/record", [digest_record, record])
428
+ writer.write(grouped)
429
+
430
+ with RecordReader(f"{db.scheme}://{db_path}", selector="r.name == 'record5'") as reader:
431
+ records = list(reader)
432
+ assert len(records) == 1
433
+ assert records[0].name == "record5"
434
+ assert records[0].digest == (
435
+ f"(md5={hashlib.md5(b'record5').hexdigest()}, "
436
+ f"sha1={hashlib.sha1(b'record5').hexdigest()}, "
437
+ f"sha256={hashlib.sha256(b'record5').hexdigest()})"
438
+ )
@@ -178,15 +178,15 @@ def test_grouped_record() -> None:
178
178
  grouped.hello = "new value"
179
179
  assert grouped.hello == "new value"
180
180
  assert grouped.profile == "omg"
181
- assert grouped.records[0].hello == "new value"
182
- assert grouped.records[1].hello == "other hello"
181
+ assert grouped.__records__[0].hello == "new value"
182
+ assert grouped.__records__[1].hello == "other hello"
183
183
 
184
- grouped.records[1].hello = "testing"
184
+ grouped.__records__[1].hello = "testing"
185
185
  assert grouped.hello != "testing"
186
186
  assert grouped.hello == "new value"
187
- assert grouped.records[1].hello == "testing"
187
+ assert grouped.__records__[1].hello == "testing"
188
188
 
189
- assert len(grouped.records) == 2
189
+ assert len(grouped.__records__) == 2
190
190
 
191
191
  # Test grouped._asdict
192
192
  rdict = grouped._asdict()
@@ -250,7 +250,7 @@ def test_grouped_records_packing(tmp_path: Path) -> None:
250
250
  assert isinstance(record, Record)
251
251
  assert isinstance(record, GroupedRecord)
252
252
  assert record.common == "world" # first 'key' has precendence
253
- assert record.name == "grouped/ab"
253
+ assert record.__name__ == "grouped/ab"
254
254
  assert record.a_string == "hello"
255
255
  assert record.a_count == 12345
256
256
  assert record.b_count == 54321
@@ -259,12 +259,12 @@ def test_grouped_records_packing(tmp_path: Path) -> None:
259
259
  assert record._classification == "CLASSIFIED"
260
260
 
261
261
  # access 'common' on second record directly
262
- assert record.records[1].common == "bye"
262
+ assert record.__records__[1].common == "bye"
263
263
 
264
264
  # access raw records directly
265
- assert len(record.records) == 2
266
- assert record.records[0]._desc.name == "test/a"
267
- assert record.records[1]._desc.name == "test/b"
265
+ assert len(record.__records__) == 2
266
+ assert record.__records__[0]._desc.name == "test/a"
267
+ assert record.__records__[1]._desc.name == "test/b"
268
268
 
269
269
  # test using selectors
270
270
  reader = RecordReader(path, selector="r.a_count == 12345")
File without changes
File without changes