flow.record 3.22.dev7__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.
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/PKG-INFO +1 -1
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/sqlite.py +3 -3
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/base.py +17 -17
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/packer.py +1 -1
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/selector.py +1 -1
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/tools/rdump.py +9 -5
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/version.py +3 -3
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow.record.egg-info/PKG-INFO +1 -1
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_sqlite_duckdb.py +37 -1
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/record/test_record.py +10 -10
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/tools/test_rdump.py +37 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/.git-blame-ignore-revs +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/.gitattributes +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/COPYRIGHT +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/LICENSE +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/MANIFEST.in +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/README.md +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/examples/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/examples/filesystem.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/examples/passivedns.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/examples/records.json +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/examples/selectors.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/examples/tcpconn.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/archive.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/avro.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/broker.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/csvfile.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/duckdb.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/elastic.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/jsonfile.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/line.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/mongo.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/split.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/splunk.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/stream.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/text.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/adapter/xlsx.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/context.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/exceptions.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/fieldtypes/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/fieldtypes/credential.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/ip.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/ipv4.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/tcp.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/fieldtypes/net/udp.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/jsonpacker.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/stream.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/tools/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/tools/geoip.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/utils.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow/record/whitelist.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow.record.egg-info/SOURCES.txt +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow.record.egg-info/dependency_links.txt +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow.record.egg-info/entry_points.txt +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow.record.egg-info/requires.txt +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/flow.record.egg-info/top_level.txt +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/pyproject.toml +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/setup.cfg +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/_data/.gitkeep +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/_docs/Makefile +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/_docs/conf.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/_docs/index.rst +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/_utils.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_avro.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_csv.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_elastic.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_json.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_line.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_splunk.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_text.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/adapter/test_xlsx.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/conftest.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/fieldtypes/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/fieldtypes/test_boolean.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/fieldtypes/test_fieldtypes.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/fieldtypes/test_ip.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/packer/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/packer/test_json_packer.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/packer/test_packer.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/record/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/record/test_adapter.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/record/test_context.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/record/test_descriptor.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/record/test_multi_timestamp.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/selector/__init__.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/selector/test_compiled.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/selector/test_selectors.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/test_deprecations.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/test_regressions.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/test_utils.py +0 -0
- {flow_record-3.22.dev7 → flow_record-3.22.dev9}/tests/tools/__init__.py +0 -0
- {flow_record-3.22.dev7 → 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.
|
|
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
|
-
|
|
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.
|
|
251
|
-
self.
|
|
252
|
-
self.
|
|
253
|
-
self.
|
|
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.
|
|
261
|
-
self.
|
|
262
|
-
self.
|
|
260
|
+
for r in rec.__records__:
|
|
261
|
+
self.__records__.append(r)
|
|
262
|
+
self.__descriptors__.append(r._desc)
|
|
263
263
|
else:
|
|
264
|
-
self.
|
|
265
|
-
self.
|
|
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.
|
|
275
|
+
self.__flat_fields__.append(field)
|
|
276
276
|
# Flat descriptor to maintain compatibility with Record
|
|
277
277
|
|
|
278
|
-
self._desc = RecordDescriptor(self.
|
|
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.
|
|
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.
|
|
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.
|
|
324
|
-
tuple(record._pack() for record in self.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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"]
|
|
@@ -321,8 +321,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
321
321
|
root_logger.handlers.clear()
|
|
322
322
|
root_logger.addHandler(handler)
|
|
323
323
|
|
|
324
|
-
fields_to_exclude = args.exclude.split(",") if args.exclude else []
|
|
325
|
-
fields = args.fields.split(",") if args.fields else []
|
|
324
|
+
fields_to_exclude = list(filter(None, map(str.strip, args.exclude.split(",")))) if args.exclude else []
|
|
325
|
+
fields = list(filter(None, map(str.strip, args.fields.split(",")))) if args.fields else []
|
|
326
|
+
|
|
327
|
+
writer_options = {}
|
|
328
|
+
if fields:
|
|
329
|
+
writer_options["fields"] = fields
|
|
330
|
+
if fields_to_exclude:
|
|
331
|
+
writer_options["exclude"] = fields_to_exclude
|
|
326
332
|
|
|
327
333
|
if args.list_adapters:
|
|
328
334
|
list_adapters()
|
|
@@ -340,8 +346,6 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
340
346
|
}
|
|
341
347
|
uri = mode_to_uri.get(args.mode, uri)
|
|
342
348
|
qparams = {
|
|
343
|
-
"fields": args.fields,
|
|
344
|
-
"exclude": args.exclude,
|
|
345
349
|
"format_spec": args.format,
|
|
346
350
|
}
|
|
347
351
|
query = urlencode({k: v for k, v in qparams.items() if v})
|
|
@@ -393,7 +397,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
393
397
|
ret = 0
|
|
394
398
|
|
|
395
399
|
try:
|
|
396
|
-
with RecordWriter(uri) as record_writer:
|
|
400
|
+
with RecordWriter(uri, **writer_options) as record_writer:
|
|
397
401
|
for count, rec in enumerate(record_iterator, start=1): # noqa: B007
|
|
398
402
|
if args.record_source is not None:
|
|
399
403
|
rec._source = args.record_source
|
|
@@ -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.
|
|
32
|
-
__version_tuple__ = version_tuple = (3, 22, '
|
|
31
|
+
__version__ = version = '3.22.dev9'
|
|
32
|
+
__version_tuple__ = version_tuple = (3, 22, 'dev9')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
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.
|
|
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.
|
|
182
|
-
assert grouped.
|
|
181
|
+
assert grouped.__records__[0].hello == "new value"
|
|
182
|
+
assert grouped.__records__[1].hello == "other hello"
|
|
183
183
|
|
|
184
|
-
grouped.
|
|
184
|
+
grouped.__records__[1].hello = "testing"
|
|
185
185
|
assert grouped.hello != "testing"
|
|
186
186
|
assert grouped.hello == "new value"
|
|
187
|
-
assert grouped.
|
|
187
|
+
assert grouped.__records__[1].hello == "testing"
|
|
188
188
|
|
|
189
|
-
assert len(grouped.
|
|
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.
|
|
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.
|
|
262
|
+
assert record.__records__[1].common == "bye"
|
|
263
263
|
|
|
264
264
|
# access raw records directly
|
|
265
|
-
assert len(record.
|
|
266
|
-
assert record.
|
|
267
|
-
assert record.
|
|
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")
|
|
@@ -904,3 +904,40 @@ def test_rdump_print_error_notes(
|
|
|
904
904
|
rdump.main([str(path), "-vvv"])
|
|
905
905
|
|
|
906
906
|
capsys.readouterr()
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
def test_rdump_fields_with_spaces(tmp_path: Path, capsysbinary: pytest.CaptureFixture) -> None:
|
|
910
|
+
"""Test if rdump handles spaces in field names gracefully."""
|
|
911
|
+
TestRecord = RecordDescriptor(
|
|
912
|
+
"test/record",
|
|
913
|
+
[
|
|
914
|
+
("varint", "count"),
|
|
915
|
+
("string", "foo"),
|
|
916
|
+
("string", "bar"),
|
|
917
|
+
],
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
path = tmp_path / "test.records"
|
|
921
|
+
out_path = tmp_path / "out.records"
|
|
922
|
+
with RecordWriter(path) as writer:
|
|
923
|
+
writer.write(TestRecord(count=0, foo="bar", bar="baz"))
|
|
924
|
+
|
|
925
|
+
# test if fields works with spaces in the name
|
|
926
|
+
rdump.main([str(path), "--fields", "foo, count ", "-w", str(out_path)])
|
|
927
|
+
with RecordReader(out_path) as reader:
|
|
928
|
+
records = list(reader)
|
|
929
|
+
assert len(records) == 1
|
|
930
|
+
assert list(records[0]._desc.fields.keys()) == ["foo", "count"]
|
|
931
|
+
|
|
932
|
+
# test if exclude works with spaces in the field names
|
|
933
|
+
rdump.main([str(path), "--exclude", " foo, bar ", "-w", str(out_path)])
|
|
934
|
+
with RecordReader(out_path) as reader:
|
|
935
|
+
records = list(reader)
|
|
936
|
+
assert len(records) == 1
|
|
937
|
+
assert list(records[0]._desc.fields.keys()) == ["count"]
|
|
938
|
+
|
|
939
|
+
# also test an adapter
|
|
940
|
+
rdump.main([str(path), "--exclude", " foo, bar ", "--csv"])
|
|
941
|
+
captured = capsysbinary.readouterr()
|
|
942
|
+
assert captured.err == b""
|
|
943
|
+
assert b"count,_source,_classification,_generated,_version\r\n" in captured.out
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|