flow.record 3.15.dev16__tar.gz → 3.16.dev2__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.15.dev16/flow.record.egg-info → flow_record-3.16.dev2}/PKG-INFO +2 -1
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/elastic.py +39 -6
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/fieldtypes/__init__.py +13 -2
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/version.py +2 -2
- {flow_record-3.15.dev16 → flow_record-3.16.dev2/flow.record.egg-info}/PKG-INFO +2 -1
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow.record.egg-info/SOURCES.txt +1 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow.record.egg-info/requires.txt +1 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/pyproject.toml +1 -0
- flow_record-3.16.dev2/tests/test_elastic_adapter.py +53 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_fieldtypes.py +77 -3
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/COPYRIGHT +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/LICENSE +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/MANIFEST.in +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/README.md +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/examples/filesystem.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/examples/passivedns.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/examples/records.json +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/examples/tcpconn.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/__init__.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/__init__.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/archive.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/avro.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/broker.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/csvfile.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/duckdb.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/jsonfile.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/line.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/mongo.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/split.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/splunk.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/sqlite.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/stream.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/text.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/adapter/xlsx.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/base.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/exceptions.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/fieldtypes/credential.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/fieldtypes/net/__init__.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/fieldtypes/net/ip.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/fieldtypes/net/ipv4.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/fieldtypes/net/tcp.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/fieldtypes/net/udp.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/jsonpacker.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/packer.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/selector.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/stream.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/tools/__init__.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/tools/geoip.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/tools/rdump.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/utils.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow/record/whitelist.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow.record.egg-info/dependency_links.txt +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow.record.egg-info/entry_points.txt +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/flow.record.egg-info/top_level.txt +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/setup.cfg +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/__init__.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/_utils.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/docs/Makefile +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/docs/conf.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/docs/index.rst +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/selector_explain_example.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/standalone_test.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_avro.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_avro_adapter.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_compiled_selector.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_csv_adapter.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_deprecations.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_fieldtype_ip.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_json_packer.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_json_record_adapter.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_multi_timestamp.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_packer.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_rdump.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_record.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_record_adapter.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_record_descriptor.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_regression.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_selector.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_splunk_adapter.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/test_sqlite_duckdb_adapter.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tests/utils_inspect.py +0 -0
- {flow_record-3.15.dev16 → flow_record-3.16.dev2}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flow.record
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.16.dev2
|
|
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
|
|
@@ -40,6 +40,7 @@ Requires-Dist: httpx; extra == "splunk"
|
|
|
40
40
|
Provides-Extra: test
|
|
41
41
|
Requires-Dist: flow.record[compression]; extra == "test"
|
|
42
42
|
Requires-Dist: flow.record[avro]; extra == "test"
|
|
43
|
+
Requires-Dist: flow.record[elastic]; extra == "test"
|
|
43
44
|
Requires-Dist: duckdb; (platform_python_implementation != "PyPy" and python_version < "3.12") and extra == "test"
|
|
44
45
|
Requires-Dist: pytz; (platform_python_implementation != "PyPy" and python_version < "3.12") and extra == "test"
|
|
45
46
|
|
|
@@ -2,7 +2,7 @@ import hashlib
|
|
|
2
2
|
import logging
|
|
3
3
|
import queue
|
|
4
4
|
import threading
|
|
5
|
-
from typing import Iterator, Union
|
|
5
|
+
from typing import Iterator, Optional, Union
|
|
6
6
|
|
|
7
7
|
import elasticsearch
|
|
8
8
|
import elasticsearch.helpers
|
|
@@ -22,9 +22,11 @@ Read usage: rdump elastic+[PROTOCOL]://[IP]:[PORT]?index=[INDEX]
|
|
|
22
22
|
[PROTOCOL]: http or https. Defaults to https when "+[PROTOCOL]" is omitted
|
|
23
23
|
|
|
24
24
|
Optional arguments:
|
|
25
|
+
[API_KEY]: base64 encoded api key to authenticate with (default: False)
|
|
25
26
|
[INDEX]: name of the index to use (default: records)
|
|
26
27
|
[VERIFY_CERTS]: verify certs of Elasticsearch instance (default: True)
|
|
27
28
|
[HASH_RECORD]: make record unique by hashing record [slow] (default: False)
|
|
29
|
+
[_META_*]: record metadata fields (default: None)
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
32
|
log = logging.getLogger(__name__)
|
|
@@ -38,6 +40,7 @@ class ElasticWriter(AbstractWriter):
|
|
|
38
40
|
verify_certs: Union[str, bool] = True,
|
|
39
41
|
http_compress: Union[str, bool] = True,
|
|
40
42
|
hash_record: Union[str, bool] = False,
|
|
43
|
+
api_key: Optional[str] = None,
|
|
41
44
|
**kwargs,
|
|
42
45
|
) -> None:
|
|
43
46
|
self.index = index
|
|
@@ -45,7 +48,17 @@ class ElasticWriter(AbstractWriter):
|
|
|
45
48
|
verify_certs = str(verify_certs).lower() in ("1", "true")
|
|
46
49
|
http_compress = str(http_compress).lower() in ("1", "true")
|
|
47
50
|
self.hash_record = str(hash_record).lower() in ("1", "true")
|
|
48
|
-
|
|
51
|
+
|
|
52
|
+
if not uri.lower().startswith(("http://", "https://")):
|
|
53
|
+
uri = "http://" + uri
|
|
54
|
+
|
|
55
|
+
self.es = elasticsearch.Elasticsearch(
|
|
56
|
+
uri,
|
|
57
|
+
verify_certs=verify_certs,
|
|
58
|
+
http_compress=http_compress,
|
|
59
|
+
api_key=api_key,
|
|
60
|
+
)
|
|
61
|
+
|
|
49
62
|
self.json_packer = JsonRecordPacker()
|
|
50
63
|
self.queue: queue.Queue[Union[Record, StopIteration]] = queue.Queue()
|
|
51
64
|
self.event = threading.Event()
|
|
@@ -58,25 +71,34 @@ class ElasticWriter(AbstractWriter):
|
|
|
58
71
|
|
|
59
72
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
60
73
|
|
|
74
|
+
self.metadata_fields = {}
|
|
75
|
+
for arg_key, arg_val in kwargs.items():
|
|
76
|
+
if arg_key.startswith("_meta_"):
|
|
77
|
+
self.metadata_fields[arg_key[6:]] = arg_val
|
|
78
|
+
|
|
61
79
|
def record_to_document(self, record: Record, index: str) -> dict:
|
|
62
80
|
"""Convert a record to a Elasticsearch compatible document dictionary"""
|
|
63
81
|
rdict = record._asdict()
|
|
64
82
|
|
|
65
|
-
# Store record metadata under `_record_metadata
|
|
83
|
+
# Store record metadata under `_record_metadata`.
|
|
66
84
|
rdict_meta = {
|
|
67
85
|
"descriptor": {
|
|
68
86
|
"name": record._desc.name,
|
|
69
87
|
"hash": record._desc.descriptor_hash,
|
|
70
88
|
},
|
|
71
89
|
}
|
|
90
|
+
|
|
72
91
|
# Move all dunder fields to `_record_metadata` to avoid naming clash with ES.
|
|
73
92
|
dunder_keys = [key for key in rdict if key.startswith("_")]
|
|
74
93
|
for key in dunder_keys:
|
|
75
94
|
rdict_meta[key.lstrip("_")] = rdict.pop(key)
|
|
76
|
-
|
|
95
|
+
|
|
96
|
+
# Remove _generated field from metadata to ensure determinstic documents.
|
|
77
97
|
if self.hash_record:
|
|
78
98
|
rdict_meta.pop("generated", None)
|
|
79
|
-
|
|
99
|
+
|
|
100
|
+
rdict["_record_metadata"] = rdict_meta.copy()
|
|
101
|
+
rdict["_record_metadata"].update(self.metadata_fields)
|
|
80
102
|
|
|
81
103
|
document = {
|
|
82
104
|
"_index": index,
|
|
@@ -106,6 +128,7 @@ class ElasticWriter(AbstractWriter):
|
|
|
106
128
|
):
|
|
107
129
|
if not ok:
|
|
108
130
|
log.error("Failed to insert %r", item)
|
|
131
|
+
|
|
109
132
|
self.event.set()
|
|
110
133
|
|
|
111
134
|
def write(self, record: Record) -> None:
|
|
@@ -129,6 +152,7 @@ class ElasticReader(AbstractReader):
|
|
|
129
152
|
verify_certs: Union[str, bool] = True,
|
|
130
153
|
http_compress: Union[str, bool] = True,
|
|
131
154
|
selector: Union[None, Selector, CompiledSelector] = None,
|
|
155
|
+
api_key: Optional[str] = None,
|
|
132
156
|
**kwargs,
|
|
133
157
|
) -> None:
|
|
134
158
|
self.index = index
|
|
@@ -136,7 +160,16 @@ class ElasticReader(AbstractReader):
|
|
|
136
160
|
self.selector = selector
|
|
137
161
|
verify_certs = str(verify_certs).lower() in ("1", "true")
|
|
138
162
|
http_compress = str(http_compress).lower() in ("1", "true")
|
|
139
|
-
|
|
163
|
+
|
|
164
|
+
if not uri.lower().startswith(("http://", "https://")):
|
|
165
|
+
uri = "http://" + uri
|
|
166
|
+
|
|
167
|
+
self.es = elasticsearch.Elasticsearch(
|
|
168
|
+
uri,
|
|
169
|
+
verify_certs=verify_certs,
|
|
170
|
+
http_compress=http_compress,
|
|
171
|
+
api_key=api_key,
|
|
172
|
+
)
|
|
140
173
|
|
|
141
174
|
if not verify_certs:
|
|
142
175
|
# Disable InsecureRequestWarning of urllib3, caused by the verify_certs flag.
|
|
@@ -663,7 +663,7 @@ class path(pathlib.PurePath, FieldType):
|
|
|
663
663
|
#
|
|
664
664
|
# This construction works around that by converting all path parts
|
|
665
665
|
# to strings first.
|
|
666
|
-
args = (str(arg) for arg in args)
|
|
666
|
+
args = tuple(str(arg) for arg in args)
|
|
667
667
|
elif isinstance(path_part, pathlib.PurePosixPath):
|
|
668
668
|
cls = posix_path
|
|
669
669
|
elif _is_windowslike_path(path_part):
|
|
@@ -671,7 +671,7 @@ class path(pathlib.PurePath, FieldType):
|
|
|
671
671
|
# like path separator (\).
|
|
672
672
|
cls = windows_path
|
|
673
673
|
if not PY_312:
|
|
674
|
-
args = (str(arg) for arg in args)
|
|
674
|
+
args = tuple(str(arg) for arg in args)
|
|
675
675
|
elif _is_posixlike_path(path_part):
|
|
676
676
|
# This handles any custom PurePath based implementations that don't have a
|
|
677
677
|
# windows like path separator (\).
|
|
@@ -684,13 +684,24 @@ class path(pathlib.PurePath, FieldType):
|
|
|
684
684
|
obj = super().__new__(cls)
|
|
685
685
|
else:
|
|
686
686
|
obj = cls._from_parts(args)
|
|
687
|
+
|
|
688
|
+
obj._empty_path = False
|
|
689
|
+
if not args or args == ("",):
|
|
690
|
+
obj._empty_path = True
|
|
687
691
|
return obj
|
|
688
692
|
|
|
689
693
|
def __eq__(self, other: Any) -> bool:
|
|
690
694
|
if isinstance(other, str):
|
|
691
695
|
return str(self) == other or self == self.__class__(other)
|
|
696
|
+
if self._empty_path:
|
|
697
|
+
return isinstance(other, self.__class__) and other._empty_path
|
|
692
698
|
return super().__eq__(other)
|
|
693
699
|
|
|
700
|
+
def __str__(self) -> str:
|
|
701
|
+
if self._empty_path:
|
|
702
|
+
return ""
|
|
703
|
+
return super().__str__()
|
|
704
|
+
|
|
694
705
|
def __repr__(self) -> str:
|
|
695
706
|
return repr(str(self))
|
|
696
707
|
|
|
@@ -12,5 +12,5 @@ __version__: str
|
|
|
12
12
|
__version_tuple__: VERSION_TUPLE
|
|
13
13
|
version_tuple: VERSION_TUPLE
|
|
14
14
|
|
|
15
|
-
__version__ = version = '3.
|
|
16
|
-
__version_tuple__ = version_tuple = (3,
|
|
15
|
+
__version__ = version = '3.16.dev2'
|
|
16
|
+
__version_tuple__ = version_tuple = (3, 16, 'dev2')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flow.record
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.16.dev2
|
|
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
|
|
@@ -40,6 +40,7 @@ Requires-Dist: httpx; extra == "splunk"
|
|
|
40
40
|
Provides-Extra: test
|
|
41
41
|
Requires-Dist: flow.record[compression]; extra == "test"
|
|
42
42
|
Requires-Dist: flow.record[avro]; extra == "test"
|
|
43
|
+
Requires-Dist: flow.record[elastic]; extra == "test"
|
|
43
44
|
Requires-Dist: duckdb; (platform_python_implementation != "PyPy" and python_version < "3.12") and extra == "test"
|
|
44
45
|
Requires-Dist: pytz; (platform_python_implementation != "PyPy" and python_version < "3.12") and extra == "test"
|
|
45
46
|
|
|
@@ -59,6 +59,7 @@ splunk = [
|
|
|
59
59
|
test = [
|
|
60
60
|
"flow.record[compression]",
|
|
61
61
|
"flow.record[avro]",
|
|
62
|
+
"flow.record[elastic]",
|
|
62
63
|
"duckdb; platform_python_implementation != 'PyPy' and python_version < '3.12'", # duckdb
|
|
63
64
|
"pytz; platform_python_implementation != 'PyPy' and python_version < '3.12'", # duckdb
|
|
64
65
|
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from flow.record import RecordDescriptor
|
|
6
|
+
from flow.record.adapter.elastic import ElasticWriter
|
|
7
|
+
|
|
8
|
+
MyRecord = RecordDescriptor(
|
|
9
|
+
"my/record",
|
|
10
|
+
[
|
|
11
|
+
("string", "field_one"),
|
|
12
|
+
("string", "field_two"),
|
|
13
|
+
],
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
"record",
|
|
19
|
+
[
|
|
20
|
+
MyRecord("first", "record"),
|
|
21
|
+
MyRecord("second", "record"),
|
|
22
|
+
],
|
|
23
|
+
)
|
|
24
|
+
def test_elastic_writer_metadata(record):
|
|
25
|
+
options = {
|
|
26
|
+
"_meta_foo": "some value",
|
|
27
|
+
"_meta_bar": "another value",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
with ElasticWriter(uri="elasticsearch:9200", **options) as writer:
|
|
31
|
+
assert writer.metadata_fields == {"foo": "some value", "bar": "another value"}
|
|
32
|
+
|
|
33
|
+
assert writer.record_to_document(record, "some-index") == {
|
|
34
|
+
"_index": "some-index",
|
|
35
|
+
"_source": json.dumps(
|
|
36
|
+
{
|
|
37
|
+
"field_one": record.field_one,
|
|
38
|
+
"field_two": record.field_two,
|
|
39
|
+
"_record_metadata": {
|
|
40
|
+
"descriptor": {
|
|
41
|
+
"name": "my/record",
|
|
42
|
+
"hash": record._desc.descriptor_hash,
|
|
43
|
+
},
|
|
44
|
+
"source": None,
|
|
45
|
+
"classification": None,
|
|
46
|
+
"generated": record._generated.isoformat(),
|
|
47
|
+
"version": 1,
|
|
48
|
+
"foo": "some value",
|
|
49
|
+
"bar": "another value",
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
}
|
|
@@ -11,7 +11,7 @@ from datetime import datetime, timedelta, timezone
|
|
|
11
11
|
import pytest
|
|
12
12
|
|
|
13
13
|
import flow.record.fieldtypes
|
|
14
|
-
from flow.record import RecordDescriptor, RecordReader, RecordWriter
|
|
14
|
+
from flow.record import RecordDescriptor, RecordReader, RecordWriter, fieldtypes
|
|
15
15
|
from flow.record.fieldtypes import (
|
|
16
16
|
PY_312,
|
|
17
17
|
TYPE_POSIX,
|
|
@@ -617,8 +617,8 @@ def test_path():
|
|
|
617
617
|
assert r.value is None
|
|
618
618
|
|
|
619
619
|
r = TestRecord("")
|
|
620
|
-
assert str(r.value) == "
|
|
621
|
-
assert r.value == "
|
|
620
|
+
assert str(r.value) == ""
|
|
621
|
+
assert r.value == ""
|
|
622
622
|
|
|
623
623
|
if os.name == "nt":
|
|
624
624
|
native_path_str = windows_path_str
|
|
@@ -1132,5 +1132,79 @@ def test_command_failed() -> None:
|
|
|
1132
1132
|
command(b"failed")
|
|
1133
1133
|
|
|
1134
1134
|
|
|
1135
|
+
@pytest.mark.parametrize(
|
|
1136
|
+
"path_cls",
|
|
1137
|
+
[
|
|
1138
|
+
fieldtypes.posix_path,
|
|
1139
|
+
fieldtypes.windows_path,
|
|
1140
|
+
fieldtypes.path,
|
|
1141
|
+
],
|
|
1142
|
+
)
|
|
1143
|
+
def test_empty_path(path_cls) -> None:
|
|
1144
|
+
# initialize with empty string
|
|
1145
|
+
p1 = path_cls("")
|
|
1146
|
+
assert p1 == ""
|
|
1147
|
+
assert p1._empty_path
|
|
1148
|
+
assert str(p1) == ""
|
|
1149
|
+
assert p1 != path_cls(".")
|
|
1150
|
+
|
|
1151
|
+
# initialize without any arguments
|
|
1152
|
+
p2 = path_cls()
|
|
1153
|
+
assert p2 == ""
|
|
1154
|
+
assert p2._empty_path
|
|
1155
|
+
assert str(p2) == ""
|
|
1156
|
+
assert p2 != path_cls(".")
|
|
1157
|
+
|
|
1158
|
+
assert p1 == p2
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
def test_empty_path_different_types() -> None:
|
|
1162
|
+
assert fieldtypes.posix_path("") != fieldtypes.windows_path("")
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
def test_record_empty_path() -> None:
|
|
1166
|
+
TestRecord = RecordDescriptor(
|
|
1167
|
+
"test/path",
|
|
1168
|
+
[
|
|
1169
|
+
("path", "value"),
|
|
1170
|
+
],
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
r = TestRecord()
|
|
1174
|
+
assert r.value is None
|
|
1175
|
+
assert repr(r) == "<test/path value=None>"
|
|
1176
|
+
|
|
1177
|
+
r = TestRecord("")
|
|
1178
|
+
assert r.value == ""
|
|
1179
|
+
assert repr(r) == "<test/path value=''>"
|
|
1180
|
+
|
|
1181
|
+
|
|
1182
|
+
def test_empty_path_serialization(tmp_path) -> None:
|
|
1183
|
+
TestRecord = RecordDescriptor(
|
|
1184
|
+
"test/path",
|
|
1185
|
+
[
|
|
1186
|
+
("path", "value"),
|
|
1187
|
+
],
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
# Test path value=None serialization
|
|
1191
|
+
p_tmp_records = tmp_path / "none_path"
|
|
1192
|
+
with RecordWriter(p_tmp_records) as writer:
|
|
1193
|
+
record = TestRecord()
|
|
1194
|
+
writer.write(record)
|
|
1195
|
+
with RecordReader(p_tmp_records) as reader:
|
|
1196
|
+
for record in reader:
|
|
1197
|
+
assert record.value is None
|
|
1198
|
+
|
|
1199
|
+
# Test path value="" serialization
|
|
1200
|
+
p_tmp_records = tmp_path / "empty_str"
|
|
1201
|
+
with RecordWriter(p_tmp_records) as writer:
|
|
1202
|
+
record = TestRecord("")
|
|
1203
|
+
writer.write(record)
|
|
1204
|
+
with RecordReader(p_tmp_records) as reader:
|
|
1205
|
+
for record in reader:
|
|
1206
|
+
assert record.value == ""
|
|
1207
|
+
|
|
1208
|
+
|
|
1135
1209
|
if __name__ == "__main__":
|
|
1136
1210
|
__import__("standalone_test").main(globals())
|
|
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
|