flow.record 3.13.dev4__tar.gz → 3.13.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 (77) hide show
  1. {flow.record-3.13.dev4/flow.record.egg-info → flow.record-3.13.dev6}/PKG-INFO +1 -1
  2. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/__init__.py +2 -2
  3. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/avro.py +2 -2
  4. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/jsonfile.py +2 -2
  5. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/line.py +2 -2
  6. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/stream.py +3 -3
  7. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/text.py +2 -2
  8. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/xlsx.py +2 -2
  9. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/base.py +44 -37
  10. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/selector.py +7 -0
  11. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/version.py +2 -2
  12. {flow.record-3.13.dev4 → flow.record-3.13.dev6/flow.record.egg-info}/PKG-INFO +1 -1
  13. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_record_adapter.py +33 -8
  14. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_selector.py +24 -0
  15. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/COPYRIGHT +0 -0
  16. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/LICENSE +0 -0
  17. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/MANIFEST.in +0 -0
  18. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/README.md +0 -0
  19. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/examples/filesystem.py +0 -0
  20. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/examples/passivedns.py +0 -0
  21. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/examples/records.json +0 -0
  22. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/examples/tcpconn.py +0 -0
  23. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/__init__.py +0 -0
  24. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/archive.py +0 -0
  25. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/broker.py +0 -0
  26. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/csvfile.py +0 -0
  27. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/elastic.py +0 -0
  28. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/mongo.py +0 -0
  29. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/split.py +0 -0
  30. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/adapter/splunk.py +0 -0
  31. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/exceptions.py +0 -0
  32. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/__init__.py +0 -0
  33. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/credential.py +0 -0
  34. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/__init__.py +0 -0
  35. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/ip.py +0 -0
  36. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/ipv4.py +0 -0
  37. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/tcp.py +0 -0
  38. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/fieldtypes/net/udp.py +0 -0
  39. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/jsonpacker.py +0 -0
  40. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/packer.py +0 -0
  41. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/stream.py +0 -0
  42. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/tools/__init__.py +0 -0
  43. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/tools/geoip.py +0 -0
  44. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/tools/rdump.py +0 -0
  45. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/utils.py +0 -0
  46. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow/record/whitelist.py +0 -0
  47. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/SOURCES.txt +0 -0
  48. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/dependency_links.txt +0 -0
  49. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/entry_points.txt +0 -0
  50. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/requires.txt +0 -0
  51. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/flow.record.egg-info/top_level.txt +0 -0
  52. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/pyproject.toml +0 -0
  53. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/setup.cfg +0 -0
  54. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/__init__.py +0 -0
  55. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/_utils.py +0 -0
  56. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/docs/Makefile +0 -0
  57. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/docs/conf.py +0 -0
  58. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/docs/index.rst +0 -0
  59. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/selector_explain_example.py +0 -0
  60. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/standalone_test.py +0 -0
  61. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_avro.py +0 -0
  62. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_avro_adapter.py +0 -0
  63. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_compiled_selector.py +0 -0
  64. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_deprecations.py +0 -0
  65. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_fieldtype_ip.py +0 -0
  66. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_fieldtypes.py +0 -0
  67. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_json_packer.py +0 -0
  68. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_json_record_adapter.py +0 -0
  69. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_multi_timestamp.py +0 -0
  70. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_packer.py +0 -0
  71. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_rdump.py +0 -0
  72. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_record.py +0 -0
  73. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_record_descriptor.py +0 -0
  74. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_regression.py +0 -0
  75. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/test_splunk_adapter.py +0 -0
  76. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tests/utils_inspect.py +0 -0
  77. {flow.record-3.13.dev4 → flow.record-3.13.dev6}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flow.record
3
- Version: 3.13.dev4
3
+ Version: 3.13.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: Affero General Public License v3
@@ -17,8 +17,8 @@ from flow.record.base import (
17
17
  dynamic_fieldtype,
18
18
  extend_record,
19
19
  iter_timestamped_records,
20
- open_file,
21
20
  open_path,
21
+ open_path_or_stream,
22
22
  open_stream,
23
23
  stream,
24
24
  )
@@ -51,7 +51,7 @@ __all__ = [
51
51
  "JsonRecordPacker",
52
52
  "RecordStreamWriter",
53
53
  "RecordStreamReader",
54
- "open_file",
54
+ "open_path_or_stream",
55
55
  "open_path",
56
56
  "open_stream",
57
57
  "stream",
@@ -53,7 +53,7 @@ class AvroWriter(AbstractWriter):
53
53
  writer = None
54
54
 
55
55
  def __init__(self, path, key=None, **kwargs):
56
- self.fp = record.open_file(path, "wb")
56
+ self.fp = record.open_path_or_stream(path, "wb")
57
57
 
58
58
  self.desc = None
59
59
  self.schema = None
@@ -93,7 +93,7 @@ class AvroReader(AbstractReader):
93
93
  fp = None
94
94
 
95
95
  def __init__(self, path, selector=None, **kwargs):
96
- self.fp = record.open_file(path, "rb")
96
+ self.fp = record.open_path_or_stream(path, "rb")
97
97
  self.selector = make_selector(selector)
98
98
 
99
99
  self.reader = fastavro.reader(self.fp)
@@ -23,7 +23,7 @@ class JsonfileWriter(AbstractWriter):
23
23
 
24
24
  def __init__(self, path, indent=None, descriptors=True, **kwargs):
25
25
  self.descriptors = str(descriptors).lower() in ("true", "1")
26
- self.fp = record.open_path(path, "w")
26
+ self.fp = record.open_path_or_stream(path, "w")
27
27
  if isinstance(indent, str):
28
28
  indent = int(indent)
29
29
  self.packer = JsonRecordPacker(indent=indent, pack_descriptors=self.descriptors)
@@ -55,7 +55,7 @@ class JsonfileReader(AbstractReader):
55
55
 
56
56
  def __init__(self, path, selector=None, **kwargs):
57
57
  self.selector = make_selector(selector)
58
- self.fp = record.open_path(path, "r")
58
+ self.fp = record.open_path_or_stream(path, "r")
59
59
  self.packer = JsonRecordPacker()
60
60
 
61
61
  def close(self):
@@ -1,4 +1,4 @@
1
- from flow.record import open_path
1
+ from flow.record import open_path_or_stream
2
2
  from flow.record.adapter import AbstractWriter
3
3
  from flow.record.utils import is_stdout
4
4
 
@@ -16,7 +16,7 @@ class LineWriter(AbstractWriter):
16
16
  fp = None
17
17
 
18
18
  def __init__(self, path, fields=None, exclude=None, **kwargs):
19
- self.fp = open_path(path, "wb")
19
+ self.fp = open_path_or_stream(path, "wb")
20
20
  self.count = 0
21
21
  self.fields = fields
22
22
  self.exclude = exclude
@@ -1,6 +1,6 @@
1
1
  from typing import Iterator, Union
2
2
 
3
- from flow.record import Record, RecordOutput, RecordStreamReader, open_file, open_path
3
+ from flow.record import Record, RecordOutput, RecordStreamReader, open_path_or_stream
4
4
  from flow.record.adapter import AbstractReader, AbstractWriter
5
5
  from flow.record.selector import Selector
6
6
  from flow.record.utils import is_stdout
@@ -19,7 +19,7 @@ class StreamWriter(AbstractWriter):
19
19
  stream = None
20
20
 
21
21
  def __init__(self, path: str, clobber=True, **kwargs):
22
- self.fp = open_path(path, "wb", clobber=clobber)
22
+ self.fp = open_path_or_stream(path, "wb", clobber=clobber)
23
23
  self.stream = RecordOutput(self.fp)
24
24
 
25
25
  def write(self, record: Record) -> None:
@@ -46,7 +46,7 @@ class StreamReader(AbstractReader):
46
46
  stream = None
47
47
 
48
48
  def __init__(self, path: str, selector: Union[str, Selector] = None, **kwargs):
49
- self.fp = open_file(path, "rb")
49
+ self.fp = open_path_or_stream(path, "rb")
50
50
  self.stream = RecordStreamReader(self.fp, selector=selector)
51
51
 
52
52
  def __iter__(self) -> Iterator[Record]:
@@ -1,4 +1,4 @@
1
- from flow.record import open_path
1
+ from flow.record import open_path_or_stream
2
2
  from flow.record.adapter import AbstractWriter
3
3
  from flow.record.utils import is_stdout
4
4
 
@@ -27,7 +27,7 @@ class TextWriter(AbstractWriter):
27
27
  fp = None
28
28
 
29
29
  def __init__(self, path, flush=True, format_spec=None, **kwargs):
30
- self.fp = open_path(path, "wb")
30
+ self.fp = open_path_or_stream(path, "wb")
31
31
  self.auto_flush = flush
32
32
  self.format_spec = format_spec
33
33
 
@@ -19,7 +19,7 @@ class XlsxWriter(AbstractWriter):
19
19
  wb = None
20
20
 
21
21
  def __init__(self, path, **kwargs):
22
- self.fp = record.open_path(path, "wb")
22
+ self.fp = record.open_path_or_stream(path, "wb")
23
23
  self.wb = openpyxl.Workbook()
24
24
  self.ws = self.wb.active
25
25
  self.desc = None
@@ -51,7 +51,7 @@ class XlsxReader(AbstractReader):
51
51
 
52
52
  def __init__(self, path, selector=None, **kwargs):
53
53
  self.selector = make_selector(selector)
54
- self.fp = record.open_path(path, "rb")
54
+ self.fp = record.open_path_or_stream(path, "rb")
55
55
  self.desc = None
56
56
  self.wb = openpyxl.load_workbook(self.fp)
57
57
  self.ws = self.wb.active
@@ -645,6 +645,8 @@ def DynamicDescriptor(name, fields):
645
645
 
646
646
 
647
647
  def open_stream(fp: BinaryIO, mode: str) -> BinaryIO:
648
+ if "w" in mode:
649
+ return fp
648
650
  if not hasattr(fp, "peek"):
649
651
  fp = io.BufferedReader(fp)
650
652
 
@@ -682,13 +684,13 @@ def find_adapter_for_stream(fp: BinaryIO) -> tuple[BinaryIO, Optional[str]]:
682
684
  return fp, None
683
685
 
684
686
 
685
- def open_file(path: Union[str, Path, BinaryIO], mode: str, clobber: bool = True) -> IO:
687
+ def open_path_or_stream(path: Union[str, Path, BinaryIO], mode: str, clobber: bool = True) -> IO:
686
688
  if isinstance(path, Path):
687
689
  path = str(path)
688
690
  if isinstance(path, str):
689
691
  return open_path(path, mode, clobber)
690
692
  elif isinstance(path, io.IOBase):
691
- return open_stream(path, "rb")
693
+ return open_stream(path, mode)
692
694
  else:
693
695
  raise ValueError(f"Unsupported path type {path}")
694
696
 
@@ -798,41 +800,45 @@ def RecordAdapter(
798
800
  cls_url = p.netloc + p.path
799
801
  if sub_adapter:
800
802
  cls_url = sub_adapter + "://" + cls_url
801
- elif url in ("-", ""):
802
- # For reading stdin, we cannot rely on an extension to know what sort of stream is incoming. Thus, we will treat
803
- # it as a 'fileobj', where we can peek into the stream and try to select the appropriate adapter.
804
- fileobj = getattr(sys.stdin, "buffer", sys.stdin)
805
- if fileobj is not None:
806
- # This record adapter has received a file-like object for record reading
807
- # We just need to find the right adapter by peeking into the first few bytes.
808
-
809
- # First, we open the stream. If the stream is compressed, open_stream will wrap it for us into a decompressor.
810
- cls_stream = open_stream(fileobj, "rb")
811
-
812
- # Now, we have a stream that will be transparently decompressed but we still do not know what adapter to use.
813
- # This requires a new peek into the transparent stream. This peek will cause the stream pointer to be moved.
814
- # Therefore, find_adapter_for_stream returns both a BinaryIO-supportive object that can correctly read the
815
- # adjusted stream, and a string indicating the type of adapter to be used on said stream.
816
- arg_dict = kwargs.copy()
817
-
818
- # If a user did not provide a url, we have to peek into the stream to be able to determine the right adapter
819
- # based on magic bytes encountered in the first few bytes of the stream.
820
- if adapter is None:
821
- cls_stream, adapter = find_adapter_for_stream(cls_stream)
803
+ if out is False:
804
+ if url in ("-", ""):
805
+ # For reading stdin, we cannot rely on an extension to know what sort of stream is incoming. Thus, we will
806
+ # treat it as a 'fileobj', where we can peek into the stream and try to select the appropriate adapter.
807
+ fileobj = getattr(sys.stdin, "buffer", sys.stdin)
808
+ if fileobj is not None:
809
+ # This record adapter has received a file-like object for record reading
810
+ # We just need to find the right adapter by peeking into the first few bytes.
811
+
812
+ # First, we open the stream. If the stream is compressed, open_stream will wrap it for us into a
813
+ # decompressor.
814
+ cls_stream = open_stream(fileobj, "rb")
815
+
816
+ # If a user did not provide a url, we have to peek into the stream to be able to determine the right adapter
817
+ # based on magic bytes encountered in the first few bytes of the stream.
822
818
  if adapter is None:
823
- peek_data = cls_stream.peek(RECORDSTREAM_MAGIC_DEPTH)
824
- if peek_data and peek_data.startswith(b"<"):
825
- # As peek() can result in a larger buffer than requested, we make sure the peek_data variable isn't
826
- # unnecessarily long in the error message.
827
- peek_data = peek_data[:RECORDSTREAM_MAGIC_DEPTH]
828
- raise RecordAdapterNotFound(
829
- (
830
- f"Could not find a reader for input {peek_data!r}. Are you perhaps "
831
- "entering record text, rather than a record stream? This can be fixed by using "
832
- "'rdump -w -' to write a record stream to stdout."
819
+ # If we could not infere an adapter from the url, we have a stream that will be transparently
820
+ # decompressed but we still do not know what adapter to use. This requires a new peek into the
821
+ # transparent stream. This peek will cause the stream pointer to be moved. Therefore,
822
+ # find_adapter_for_stream returns both a BinaryIO-supportive object that can correctly read the adjusted
823
+ # stream, and a string indicating the type of adapter to be used on said stream.
824
+ cls_stream, adapter = find_adapter_for_stream(cls_stream)
825
+ if adapter is None:
826
+ # As peek() can result in a larger buffer than requested, so we truncate it just to be sure
827
+ peek_data = cls_stream.peek(RECORDSTREAM_MAGIC_DEPTH)[:RECORDSTREAM_MAGIC_DEPTH]
828
+ if peek_data and peek_data.startswith(b"<"):
829
+ raise RecordAdapterNotFound(
830
+ (
831
+ f"Could not find a reader for input {peek_data!r}. Are you perhaps "
832
+ "entering record text, rather than a record stream? This can be fixed by using "
833
+ "'rdump -w -' to write a record stream to stdout."
834
+ )
833
835
  )
834
- )
835
- raise RecordAdapterNotFound("Could not find adapter for file-like object")
836
+ raise RecordAdapterNotFound("Could not find adapter for file-like object")
837
+
838
+ # Now that we found an adapter, we will fall back into the same code path as when a URL is given. As the url
839
+ # parsing path copied kwargs into an arg_dict variable, we will do the same so we do not get a variable
840
+ # referenced before assignment error.
841
+ arg_dict = kwargs.copy()
836
842
 
837
843
  # Now that we know which adapter is needed, we import it.
838
844
  mod = importlib.import_module("flow.record.adapter.{}".format(adapter))
@@ -845,9 +851,10 @@ def RecordAdapter(
845
851
  if out:
846
852
  arg_dict["clobber"] = clobber
847
853
  log.debug("Creating {!r} for {!r} with args {!r}".format(cls, url, arg_dict))
848
-
849
- if fileobj is not None:
854
+ if cls_stream is not None:
850
855
  return cls(cls_stream, **arg_dict)
856
+ if fileobj is not None:
857
+ return cls(fileobj, **arg_dict)
851
858
  return cls(cls_url, **arg_dict)
852
859
 
853
860
 
@@ -355,6 +355,12 @@ class WrappedRecord:
355
355
  def __getattr__(self, k):
356
356
  return getattr(self.record, k, NONE_OBJECT)
357
357
 
358
+ def __str__(self) -> str:
359
+ return str(self.record)
360
+
361
+ def __repr__(self) -> str:
362
+ return repr(self.record)
363
+
358
364
 
359
365
  class CompiledSelector:
360
366
  """CompiledSelector is faster than Selector but unsafe if you don't trust the query."""
@@ -546,6 +552,7 @@ class RecordContextMatcher:
546
552
  "True": True,
547
553
  "False": False,
548
554
  "str": str,
555
+ "repr": repr,
549
556
  "fields": rec._desc.getfields,
550
557
  "any": any,
551
558
  "all": all,
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '3.13.dev4'
16
- __version_tuple__ = version_tuple = (3, 13, 'dev4')
15
+ __version__ = version = '3.13.dev6'
16
+ __version_tuple__ = version_tuple = (3, 13, 'dev6')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flow.record
3
- Version: 3.13.dev4
3
+ Version: 3.13.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: Affero General Public License v3
@@ -1,16 +1,13 @@
1
1
  import datetime
2
2
  import platform
3
3
  import sys
4
+ from io import BytesIO
4
5
 
5
6
  import pytest
6
7
 
7
- try:
8
- from StringIO import StringIO
9
- except ImportError:
10
- from io import BytesIO as StringIO
11
-
12
8
  from flow.record import (
13
9
  PathTemplateWriter,
10
+ RecordAdapter,
14
11
  RecordArchiver,
15
12
  RecordDescriptor,
16
13
  RecordOutput,
@@ -18,7 +15,7 @@ from flow.record import (
18
15
  RecordStreamReader,
19
16
  RecordWriter,
20
17
  )
21
- from flow.record.adapter.stream import StreamReader
18
+ from flow.record.adapter.stream import StreamReader, StreamWriter
22
19
  from flow.record.base import (
23
20
  BZ2_MAGIC,
24
21
  GZIP_MAGIC,
@@ -33,7 +30,7 @@ from ._utils import generate_records
33
30
 
34
31
 
35
32
  def test_stream_writer_reader():
36
- fp = StringIO()
33
+ fp = BytesIO()
37
34
  out = RecordOutput(fp)
38
35
  for rec in generate_records():
39
36
  out.write(rec)
@@ -48,7 +45,7 @@ def test_stream_writer_reader():
48
45
 
49
46
 
50
47
  def test_recordstream_filelike_object():
51
- fp = StringIO()
48
+ fp = BytesIO()
52
49
  out = RecordOutput(fp)
53
50
  for rec in generate_records():
54
51
  out.write(rec)
@@ -477,3 +474,31 @@ def test_csvfilereader(tmp_path):
477
474
  with RecordReader(f"csvfile://{path}", selector="r.count == '2'") as reader:
478
475
  for i, rec in enumerate(reader):
479
476
  assert rec.count == "2"
477
+
478
+
479
+ def test_file_like_writer_reader() -> None:
480
+ test_buf = BytesIO()
481
+
482
+ adapter = RecordAdapter(fileobj=test_buf, out=True)
483
+
484
+ assert isinstance(adapter, StreamWriter)
485
+
486
+ # Add mock records
487
+ test_records = list(generate_records(10))
488
+ for record in test_records:
489
+ adapter.write(record)
490
+
491
+ adapter.flush()
492
+
493
+ # Grab the bytes before closing the BytesIO object.
494
+ read_buf = BytesIO(test_buf.getvalue())
495
+
496
+ # Close the writer and assure the object has been closed
497
+ adapter.close()
498
+
499
+ # Verify if the written record stream is something we can read
500
+ reader = RecordAdapter(fileobj=read_buf)
501
+ read_records = list(reader)
502
+ assert len(read_records) == 10
503
+ for idx, record in enumerate(read_records):
504
+ assert record == test_records[idx]
@@ -53,6 +53,30 @@ def test_selector():
53
53
  assert TestRecord() in Selector("invalid_func(r.invalid_field, 1337) or r.id == 4")
54
54
 
55
55
 
56
+ def test_selector_str_repr():
57
+ TestRecord = RecordDescriptor(
58
+ "test/record",
59
+ [
60
+ ("string", "query"),
61
+ ("string", "url"),
62
+ ],
63
+ )
64
+
65
+ assert TestRecord("foo", "bar") in Selector("'foo' in str(r)")
66
+ assert TestRecord("foo", "bar") in Selector("'test/record' in str(r)")
67
+ assert TestRecord("foo", "bar") in Selector("'foo' in repr(r)")
68
+ assert TestRecord("foo", "bar") in Selector("'test/record' in repr(r)")
69
+ assert TestRecord("foo", "bar") in CompiledSelector("'foo' in str(r)")
70
+ assert TestRecord("foo", "bar") in CompiledSelector("'test/record' in str(r)")
71
+ assert TestRecord("foo", "bar") in CompiledSelector("'foo' in repr(r)")
72
+ assert TestRecord("foo", "bar") in CompiledSelector("'test/record' in repr(r)")
73
+
74
+ assert TestRecord("foo", "bar") not in Selector("'nope' in str(r)")
75
+ assert TestRecord("foo", "bar") not in Selector("'nope' in repr(r)")
76
+ assert TestRecord("foo", "bar") not in CompiledSelector("'nope' in str(r)")
77
+ assert TestRecord("foo", "bar") not in CompiledSelector("'nope' in repr(r)")
78
+
79
+
56
80
  def test_selector_meta_query_true():
57
81
  source = "internal/flow.record.test"
58
82
 
File without changes
File without changes