flow.record 3.22.dev4__tar.gz → 3.22.dev6__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.dev4 → flow_record-3.22.dev6}/PKG-INFO +1 -1
  2. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/sqlite.py +5 -0
  3. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/jsonpacker.py +14 -7
  4. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/version.py +3 -3
  5. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow.record.egg-info/PKG-INFO +1 -1
  6. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow.record.egg-info/SOURCES.txt +1 -0
  7. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_sqlite_duckdb.py +13 -8
  8. flow_record-3.22.dev6/tests/fieldtypes/test_boolean.py +35 -0
  9. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/fieldtypes/test_fieldtypes.py +0 -30
  10. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/packer/test_json_packer.py +10 -0
  11. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/.git-blame-ignore-revs +0 -0
  12. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/.gitattributes +0 -0
  13. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/COPYRIGHT +0 -0
  14. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/LICENSE +0 -0
  15. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/MANIFEST.in +0 -0
  16. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/README.md +0 -0
  17. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/examples/__init__.py +0 -0
  18. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/examples/filesystem.py +0 -0
  19. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/examples/passivedns.py +0 -0
  20. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/examples/records.json +0 -0
  21. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/examples/selectors.py +0 -0
  22. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/examples/tcpconn.py +0 -0
  23. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/__init__.py +0 -0
  24. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/__init__.py +0 -0
  25. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/archive.py +0 -0
  26. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/avro.py +0 -0
  27. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/broker.py +0 -0
  28. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/csvfile.py +0 -0
  29. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/duckdb.py +0 -0
  30. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/elastic.py +0 -0
  31. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/jsonfile.py +0 -0
  32. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/line.py +0 -0
  33. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/mongo.py +0 -0
  34. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/split.py +0 -0
  35. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/splunk.py +0 -0
  36. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/stream.py +0 -0
  37. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/text.py +0 -0
  38. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/adapter/xlsx.py +0 -0
  39. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/base.py +0 -0
  40. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/context.py +0 -0
  41. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/exceptions.py +0 -0
  42. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/fieldtypes/__init__.py +0 -0
  43. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/fieldtypes/credential.py +0 -0
  44. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/fieldtypes/net/__init__.py +0 -0
  45. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/fieldtypes/net/ip.py +0 -0
  46. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/fieldtypes/net/ipv4.py +0 -0
  47. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/fieldtypes/net/tcp.py +0 -0
  48. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/fieldtypes/net/udp.py +0 -0
  49. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/packer.py +0 -0
  50. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/selector.py +0 -0
  51. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/stream.py +0 -0
  52. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/tools/__init__.py +0 -0
  53. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/tools/geoip.py +0 -0
  54. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/tools/rdump.py +0 -0
  55. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/utils.py +0 -0
  56. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow/record/whitelist.py +0 -0
  57. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow.record.egg-info/dependency_links.txt +0 -0
  58. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow.record.egg-info/entry_points.txt +0 -0
  59. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow.record.egg-info/requires.txt +0 -0
  60. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/flow.record.egg-info/top_level.txt +0 -0
  61. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/pyproject.toml +0 -0
  62. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/setup.cfg +0 -0
  63. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/__init__.py +0 -0
  64. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/_data/.gitkeep +0 -0
  65. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/_docs/Makefile +0 -0
  66. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/_docs/conf.py +0 -0
  67. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/_docs/index.rst +0 -0
  68. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/_utils.py +0 -0
  69. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/__init__.py +0 -0
  70. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_avro.py +0 -0
  71. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_csv.py +0 -0
  72. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_elastic.py +0 -0
  73. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_json.py +0 -0
  74. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_line.py +0 -0
  75. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_splunk.py +0 -0
  76. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_text.py +0 -0
  77. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/adapter/test_xlsx.py +0 -0
  78. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/conftest.py +0 -0
  79. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/fieldtypes/__init__.py +0 -0
  80. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/fieldtypes/test_ip.py +0 -0
  81. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/packer/__init__.py +0 -0
  82. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/packer/test_packer.py +0 -0
  83. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/record/__init__.py +0 -0
  84. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/record/test_adapter.py +0 -0
  85. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/record/test_context.py +0 -0
  86. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/record/test_descriptor.py +0 -0
  87. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/record/test_multi_timestamp.py +0 -0
  88. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/record/test_record.py +0 -0
  89. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/selector/__init__.py +0 -0
  90. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/selector/test_compiled.py +0 -0
  91. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/selector/test_selectors.py +0 -0
  92. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/test_deprecations.py +0 -0
  93. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/test_regressions.py +0 -0
  94. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/test_utils.py +0 -0
  95. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/tools/__init__.py +0 -0
  96. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tests/tools/test_rdump.py +0 -0
  97. {flow_record-3.22.dev4 → flow_record-3.22.dev6}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flow.record
3
- Version: 3.22.dev4
3
+ Version: 3.22.dev6
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
@@ -204,6 +204,11 @@ class SqliteReader(AbstractReader):
204
204
  if match_record_with_context(record, selector, ctx):
205
205
  yield record
206
206
 
207
+ def close(self) -> None:
208
+ if self.con:
209
+ self.con.close()
210
+ self.con = None
211
+
207
212
 
208
213
  class SqliteWriter(AbstractWriter):
209
214
  """SQLite writer."""
@@ -49,12 +49,7 @@ class JsonRecordPacker:
49
49
  serial["_type"] = "record"
50
50
  serial["_recorddescriptor"] = obj._desc.identifier
51
51
 
52
- for field_type, field_name in obj._desc.get_field_tuples():
53
- # Boolean field types should be cast to a bool instead of staying ints
54
- if field_type == "boolean" and isinstance(serial[field_name], int):
55
- serial[field_name] = bool(serial[field_name])
56
-
57
- return serial
52
+ return self.convert_basic_types(serial)
58
53
  if isinstance(obj, RecordDescriptor):
59
54
  return {
60
55
  "_type": "recorddescriptor",
@@ -102,7 +97,19 @@ class JsonRecordPacker:
102
97
  return RecordDescriptor._unpack(*data)
103
98
  return obj
104
99
 
105
- def pack(self, obj: Record | RecordDescriptor) -> str:
100
+ def convert_basic_types(self, obj: Any) -> Any:
101
+ """Explicitly convert some basic types when packing to JSON."""
102
+ if isinstance(obj, fieldtypes.boolean):
103
+ return bool(obj)
104
+ if isinstance(obj, dict):
105
+ return {k: self.convert_basic_types(v) for k, v in obj.items()}
106
+ if isinstance(obj, list):
107
+ return [self.convert_basic_types(item) for item in obj]
108
+ return obj
109
+
110
+ def pack(self, obj: Record | RecordDescriptor | dict) -> str:
111
+ if isinstance(obj, dict):
112
+ obj = self.convert_basic_types(obj)
106
113
  return json.dumps(obj, default=self.pack_obj, indent=self.indent)
107
114
 
108
115
  def unpack(self, d: str) -> RecordDescriptor | Record:
@@ -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.dev4'
32
- __version_tuple__ = version_tuple = (3, 22, 'dev4')
31
+ __version__ = version = '3.22.dev6'
32
+ __version_tuple__ = version_tuple = (3, 22, 'dev6')
33
33
 
34
- __commit_id__ = commit_id = 'g4bd45720d'
34
+ __commit_id__ = commit_id = 'g668138538'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flow.record
3
- Version: 3.22.dev4
3
+ Version: 3.22.dev6
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
@@ -76,6 +76,7 @@ tests/adapter/test_sqlite_duckdb.py
76
76
  tests/adapter/test_text.py
77
77
  tests/adapter/test_xlsx.py
78
78
  tests/fieldtypes/__init__.py
79
+ tests/fieldtypes/test_boolean.py
79
80
  tests/fieldtypes/test_fieldtypes.py
80
81
  tests/fieldtypes/test_ip.py
81
82
  tests/packer/__init__.py
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sqlite3
4
+ from contextlib import closing
4
5
  from datetime import datetime, timezone
5
6
  from typing import TYPE_CHECKING, Any, NamedTuple
6
7
 
@@ -136,7 +137,7 @@ def test_write_to_sqlite(tmp_path: Path, count: int, db: Database) -> None:
136
137
  writer.write(record)
137
138
 
138
139
  record_count = 0
139
- with db.connector.connect(str(db_path)) as con:
140
+ with closing(db.connector.connect(str(db_path))) as con:
140
141
  cursor = con.execute("SELECT COUNT(*) FROM 'test/record'")
141
142
  record_count = cursor.fetchone()[0]
142
143
 
@@ -157,7 +158,7 @@ def test_read_from_sqlite(tmp_path: Path, db: Database) -> None:
157
158
  """Tests basic reading from a SQLite database."""
158
159
  # Generate a SQLite database
159
160
  db_path = tmp_path / "records.db"
160
- with db.connector.connect(str(db_path)) as con:
161
+ with closing(db.connector.connect(str(db_path))) as con:
161
162
  con.execute(
162
163
  """
163
164
  CREATE TABLE 'test/record' (
@@ -176,6 +177,7 @@ def test_read_from_sqlite(tmp_path: Path, db: Database) -> None:
176
177
  """,
177
178
  (f"record{i}", f"foobar{i}".encode(), dt_isoformat, 3.14 + i),
178
179
  )
180
+ con.commit()
179
181
 
180
182
  # Read the SQLite database using flow.record
181
183
  with RecordReader(f"{db.scheme}://{db_path}") as reader:
@@ -251,7 +253,7 @@ def test_write_zero_records(tmp_path: Path, db: Database) -> None:
251
253
  assert writer
252
254
 
253
255
  # test if it's a valid database
254
- with db.connector.connect(str(db_path)) as con:
256
+ with closing(db.connector.connect(str(db_path))) as con:
255
257
  assert con.execute("SELECT * FROM sqlite_master").fetchall() == []
256
258
 
257
259
 
@@ -272,9 +274,10 @@ def test_write_zero_records(tmp_path: Path, db: Database) -> None:
272
274
  def test_non_strict_sqlite_fields(tmp_path: Path, sqlite_coltype: str, sqlite_value: Any, expected_value: Any) -> None:
273
275
  """SQLite by default is non strict, meaning that the value could be of different type than the column type."""
274
276
  db = tmp_path / "records.db"
275
- with sqlite3.connect(db) as con:
277
+ with closing(sqlite3.connect(db)) as con:
276
278
  con.execute(f"CREATE TABLE 'strict-test' (field {sqlite_coltype})")
277
279
  con.execute("INSERT INTO 'strict-test' VALUES(?)", (sqlite_value,))
280
+ con.commit()
278
281
 
279
282
  with RecordReader(f"sqlite://{db}") as reader:
280
283
  record = next(iter(reader))
@@ -294,10 +297,11 @@ def test_invalid_table_names_quoting(tmp_path: Path, invalid_table_name: str) ->
294
297
 
295
298
  # Creating the tables with these invalid_table_names in SQLite is no problem
296
299
  db = tmp_path / "records.db"
297
- with sqlite3.connect(db) as con:
300
+ with closing(sqlite3.connect(db)) as con:
298
301
  con.execute(f"CREATE TABLE [{invalid_table_name}] (field TEXT, field2 TEXT)")
299
302
  con.execute(f"INSERT INTO [{invalid_table_name}] VALUES(?, ?)", ("hello", "world"))
300
303
  con.execute(f"INSERT INTO [{invalid_table_name}] VALUES(?, ?)", ("goodbye", "planet"))
304
+ con.commit()
301
305
 
302
306
  # However, these invalid_table_names should raise an exception when reading
303
307
  with (
@@ -320,10 +324,11 @@ def test_invalid_field_names_quoting(tmp_path: Path, invalid_field_name: str) ->
320
324
 
321
325
  # Creating the table with invalid field name in SQLite is no problem
322
326
  db = tmp_path / "records.db"
323
- with sqlite3.connect(db) as con:
327
+ with closing(sqlite3.connect(db)) as con:
324
328
  con.execute(f"CREATE TABLE [test] (field TEXT, [{invalid_field_name}] TEXT)")
325
329
  con.execute("INSERT INTO [test] VALUES(?, ?)", ("hello", "world"))
326
330
  con.execute("INSERT INTO [test] VALUES(?, ?)", ("goodbye", "planet"))
331
+ con.commit()
327
332
 
328
333
  # However, these field names are invalid in flow.record and should raise an exception
329
334
  with (
@@ -365,7 +370,7 @@ def test_batch_size(
365
370
  writer.write(next(records))
366
371
 
367
372
  # test count of records in table (no flush yet if batch_size > 1)
368
- with db.connector.connect(str(db_path)) as con:
373
+ with closing(db.connector.connect(str(db_path))) as con:
369
374
  x = con.execute('SELECT COUNT(*) FROM "test/record"')
370
375
  assert x.fetchone()[0] is expected_first
371
376
 
@@ -374,7 +379,7 @@ def test_batch_size(
374
379
  writer.write(next(records))
375
380
 
376
381
  # test count of records in table after flush
377
- with db.connector.connect(str(db_path)) as con:
382
+ with closing(db.connector.connect(str(db_path))) as con:
378
383
  x = con.execute('SELECT COUNT(*) FROM "test/record"')
379
384
  assert x.fetchone()[0] == expected_second
380
385
 
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from flow.record.base import RecordDescriptor
6
+
7
+
8
+ def test_boolean() -> None:
9
+ TestRecord = RecordDescriptor(
10
+ "test/boolean",
11
+ [
12
+ ("boolean", "booltrue"),
13
+ ("boolean", "boolfalse"),
14
+ ],
15
+ )
16
+
17
+ r = TestRecord(True, False)
18
+ assert bool(r.booltrue) is True
19
+ assert bool(r.boolfalse) is False
20
+
21
+ r = TestRecord(1, 0)
22
+ assert bool(r.booltrue) is True
23
+ assert bool(r.boolfalse) is False
24
+
25
+ assert str(r.booltrue) == "True"
26
+ assert str(r.boolfalse) == "False"
27
+
28
+ assert repr(r.booltrue) == "True"
29
+ assert repr(r.boolfalse) == "False"
30
+
31
+ with pytest.raises(ValueError, match="Value not a valid boolean value"):
32
+ TestRecord(2, -1)
33
+
34
+ with pytest.raises(ValueError, match="invalid literal for int"):
35
+ TestRecord("True", "False")
@@ -293,36 +293,6 @@ def test_dictlist() -> None:
293
293
  assert r.hits[1]["b"] == 4
294
294
 
295
295
 
296
- def test_boolean() -> None:
297
- TestRecord = RecordDescriptor(
298
- "test/boolean",
299
- [
300
- ("boolean", "booltrue"),
301
- ("boolean", "boolfalse"),
302
- ],
303
- )
304
-
305
- r = TestRecord(True, False)
306
- assert bool(r.booltrue) is True
307
- assert bool(r.boolfalse) is False
308
-
309
- r = TestRecord(1, 0)
310
- assert bool(r.booltrue) is True
311
- assert bool(r.boolfalse) is False
312
-
313
- assert str(r.booltrue) == "True"
314
- assert str(r.boolfalse) == "False"
315
-
316
- assert repr(r.booltrue) == "True"
317
- assert repr(r.boolfalse) == "False"
318
-
319
- with pytest.raises(ValueError, match="Value not a valid boolean value"):
320
- r = TestRecord(2, -1)
321
-
322
- with pytest.raises(ValueError, match="invalid literal for int"):
323
- r = TestRecord("True", "False")
324
-
325
-
326
296
  def test_float() -> None:
327
297
  TestRecord = RecordDescriptor(
328
298
  "test/float",
@@ -93,6 +93,16 @@ def test_record_pack_bool_regression() -> None:
93
93
  # pack the json string back to a record and make sure it is the same as before
94
94
  assert packer.unpack(data) == record
95
95
 
96
+ # Make sure the same applies to an OrderedDict, which is how JsonRecordPacker is invoked for
97
+ # the Elastic adapter.
98
+ rdict = record._asdict()
99
+ data = packer.pack(rdict)
100
+ assert data.startswith('{"some_varint": 1, "some_uint": 0, "some_boolean": false, ')
101
+
102
+ # test that packer.pack has no side effects on rdict
103
+ assert rdict == record._asdict()
104
+ assert isinstance(rdict["some_boolean"], fieldtypes.boolean)
105
+
96
106
 
97
107
  def test_record_pack_surrogateescape() -> None:
98
108
  TestRecord = RecordDescriptor(
File without changes
File without changes