flow.record 3.21.dev2__tar.gz → 3.21.dev3__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 (86) hide show
  1. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/PKG-INFO +1 -1
  2. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/csvfile.py +8 -3
  3. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/elastic.py +6 -5
  4. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/jsonfile.py +2 -2
  5. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/splunk.py +2 -2
  6. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/tools/rdump.py +17 -3
  7. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/utils.py +29 -0
  8. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/version.py +2 -2
  9. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow.record.egg-info/PKG-INFO +1 -1
  10. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow.record.egg-info/SOURCES.txt +1 -0
  11. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_record_adapter.py +1 -1
  12. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_regression.py +20 -0
  13. flow_record-3.21.dev3/tests/test_utils.py +25 -0
  14. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/.git-blame-ignore-revs +0 -0
  15. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/COPYRIGHT +0 -0
  16. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/LICENSE +0 -0
  17. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/MANIFEST.in +0 -0
  18. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/README.md +0 -0
  19. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/examples/filesystem.py +0 -0
  20. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/examples/passivedns.py +0 -0
  21. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/examples/records.json +0 -0
  22. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/examples/tcpconn.py +0 -0
  23. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/__init__.py +0 -0
  24. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/__init__.py +0 -0
  25. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/archive.py +0 -0
  26. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/avro.py +0 -0
  27. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/broker.py +0 -0
  28. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/duckdb.py +0 -0
  29. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/line.py +0 -0
  30. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/mongo.py +0 -0
  31. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/split.py +0 -0
  32. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/sqlite.py +0 -0
  33. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/stream.py +0 -0
  34. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/text.py +0 -0
  35. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/adapter/xlsx.py +0 -0
  36. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/base.py +0 -0
  37. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/exceptions.py +0 -0
  38. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/fieldtypes/__init__.py +0 -0
  39. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/fieldtypes/credential.py +0 -0
  40. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/fieldtypes/net/__init__.py +0 -0
  41. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/fieldtypes/net/ip.py +0 -0
  42. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/fieldtypes/net/ipv4.py +0 -0
  43. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/fieldtypes/net/tcp.py +0 -0
  44. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/fieldtypes/net/udp.py +0 -0
  45. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/jsonpacker.py +0 -0
  46. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/packer.py +0 -0
  47. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/selector.py +0 -0
  48. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/stream.py +0 -0
  49. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/tools/__init__.py +0 -0
  50. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/tools/geoip.py +0 -0
  51. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow/record/whitelist.py +0 -0
  52. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow.record.egg-info/dependency_links.txt +0 -0
  53. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow.record.egg-info/entry_points.txt +0 -0
  54. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow.record.egg-info/requires.txt +0 -0
  55. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/flow.record.egg-info/top_level.txt +0 -0
  56. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/pyproject.toml +0 -0
  57. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/setup.cfg +0 -0
  58. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/__init__.py +0 -0
  59. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/_utils.py +0 -0
  60. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/docs/Makefile +0 -0
  61. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/docs/conf.py +0 -0
  62. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/docs/index.rst +0 -0
  63. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/selector_explain_example.py +0 -0
  64. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/standalone_test.py +0 -0
  65. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_adapter_line.py +0 -0
  66. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_adapter_text.py +0 -0
  67. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_avro.py +0 -0
  68. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_avro_adapter.py +0 -0
  69. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_compiled_selector.py +0 -0
  70. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_csv_adapter.py +0 -0
  71. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_deprecations.py +0 -0
  72. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_elastic_adapter.py +0 -0
  73. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_fieldtype_ip.py +0 -0
  74. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_fieldtypes.py +0 -0
  75. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_json_packer.py +0 -0
  76. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_json_record_adapter.py +0 -0
  77. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_multi_timestamp.py +0 -0
  78. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_packer.py +0 -0
  79. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_rdump.py +0 -0
  80. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_record.py +0 -0
  81. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_record_descriptor.py +0 -0
  82. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_selector.py +0 -0
  83. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_splunk_adapter.py +0 -0
  84. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_sqlite_duckdb_adapter.py +0 -0
  85. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tests/test_xlsx_adapter.py +0 -0
  86. {flow_record-3.21.dev2 → flow_record-3.21.dev3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flow.record
3
- Version: 3.21.dev2
3
+ Version: 3.21.dev3
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
@@ -9,7 +9,7 @@ from flow.record import RecordDescriptor
9
9
  from flow.record.adapter import AbstractReader, AbstractWriter
10
10
  from flow.record.base import Record, normalize_fieldname
11
11
  from flow.record.selector import make_selector
12
- from flow.record.utils import is_stdout
12
+ from flow.record.utils import boolean_argument, is_stdout
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from collections.abc import Iterator
@@ -17,11 +17,12 @@ if TYPE_CHECKING:
17
17
  __usage__ = """
18
18
  Comma-separated values (CSV) adapter
19
19
  ---
20
- Write usage: rdump -w csvfile://[PATH]?lineterminator=[TERMINATOR]
20
+ Write usage: rdump -w csvfile://[PATH]?lineterminator=[TERMINATOR]&header=[HEADER]
21
21
  Read usage: rdump csvfile://[PATH]?fields=[FIELDS]
22
22
  [PATH]: path to file. Leave empty or "-" to output to stdout
23
23
 
24
24
  Optional parameters:
25
+ [HEADER]: if set to false, it will not print the CSV header (default: true)
25
26
  [TERMINATOR]: line terminator, default is \\r\\n
26
27
  [FIELDS]: comma-separated list of CSV fields (in case of missing CSV header)
27
28
  """
@@ -34,6 +35,7 @@ class CsvfileWriter(AbstractWriter):
34
35
  fields: str | list[str] | None = None,
35
36
  exclude: str | list[str] | None = None,
36
37
  lineterminator: str = "\r\n",
38
+ header: str = "true",
37
39
  **kwargs,
38
40
  ):
39
41
  self.fp = None
@@ -52,13 +54,16 @@ class CsvfileWriter(AbstractWriter):
52
54
  self.fields = self.fields.split(",")
53
55
  if isinstance(self.exclude, str):
54
56
  self.exclude = self.exclude.split(",")
57
+ self.header = boolean_argument(header)
55
58
 
56
59
  def write(self, r: Record) -> None:
57
60
  rdict = r._asdict(fields=self.fields, exclude=self.exclude)
58
61
  if not self.desc or self.desc != r._desc:
59
62
  self.desc = r._desc
60
63
  self.writer = csv.DictWriter(self.fp, rdict, lineterminator=self.lineterminator)
61
- self.writer.writeheader()
64
+ if self.header:
65
+ # Write header only if it is requested
66
+ self.writer.writeheader()
62
67
  self.writer.writerow(rdict)
63
68
 
64
69
  def flush(self) -> None:
@@ -19,6 +19,7 @@ from flow.record.adapter import AbstractReader, AbstractWriter
19
19
  from flow.record.base import Record, RecordDescriptor
20
20
  from flow.record.fieldtypes import fieldtype_for_value
21
21
  from flow.record.jsonpacker import JsonRecordPacker
22
+ from flow.record.utils import boolean_argument
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from collections.abc import Iterator
@@ -72,9 +73,9 @@ class ElasticWriter(AbstractWriter):
72
73
 
73
74
  self.index = index
74
75
  self.uri = uri
75
- verify_certs = str(verify_certs).lower() in ("1", "true")
76
- http_compress = str(http_compress).lower() in ("1", "true")
77
- self.hash_record = str(hash_record).lower() in ("1", "true")
76
+ verify_certs = boolean_argument(verify_certs)
77
+ http_compress = boolean_argument(http_compress)
78
+ self.hash_record = boolean_argument(hash_record)
78
79
  queue_size = int(queue_size)
79
80
 
80
81
  if not uri.lower().startswith(("http://", "https://")):
@@ -216,8 +217,8 @@ class ElasticReader(AbstractReader):
216
217
  self.index = index
217
218
  self.uri = uri
218
219
  self.selector = selector
219
- verify_certs = str(verify_certs).lower() in ("1", "true")
220
- http_compress = str(http_compress).lower() in ("1", "true")
220
+ verify_certs = boolean_argument(verify_certs)
221
+ http_compress = boolean_argument(http_compress)
221
222
 
222
223
  if not uri.lower().startswith(("http://", "https://")):
223
224
  uri = "http://" + uri
@@ -8,7 +8,7 @@ from flow.record import JsonRecordPacker
8
8
  from flow.record.adapter import AbstractReader, AbstractWriter
9
9
  from flow.record.fieldtypes import fieldtype_for_value
10
10
  from flow.record.selector import make_selector
11
- from flow.record.utils import is_stdout
11
+ from flow.record.utils import boolean_argument, is_stdout
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import Iterator
@@ -33,7 +33,7 @@ class JsonfileWriter(AbstractWriter):
33
33
  def __init__(
34
34
  self, path: str | Path | BinaryIO, indent: str | int | None = None, descriptors: bool = True, **kwargs
35
35
  ):
36
- self.descriptors = str(descriptors).lower() in ("true", "1")
36
+ self.descriptors = boolean_argument(descriptors)
37
37
  self.fp = record.open_path_or_stream(path, "w")
38
38
  if isinstance(indent, str):
39
39
  indent = int(indent)
@@ -18,7 +18,7 @@ except ImportError:
18
18
 
19
19
  from flow.record.adapter import AbstractReader, AbstractWriter
20
20
  from flow.record.jsonpacker import JsonRecordPacker
21
- from flow.record.utils import to_base64, to_bytes, to_str
21
+ from flow.record.utils import boolean_argument, to_base64, to_bytes, to_str
22
22
 
23
23
  if TYPE_CHECKING:
24
24
  from flow.record.base import Record
@@ -218,7 +218,7 @@ class SplunkWriter(AbstractWriter):
218
218
  self.token = f"Splunk {self.token}"
219
219
 
220
220
  # Assume verify=True unless specified otherwise.
221
- self.verify = str(ssl_verify).lower() not in ("0", "false")
221
+ self.verify = boolean_argument(ssl_verify)
222
222
  if not self.verify:
223
223
  log.warning("Certificate verification is disabled")
224
224
 
@@ -107,7 +107,11 @@ def main(argv: list[str] | None = None) -> int:
107
107
  output.add_argument("--skip", metavar="COUNT", type=int, default=0, help="Skip the first COUNT records")
108
108
  output.add_argument("-w", "--writer", metavar="OUTPUT", default=None, help="Write records to output")
109
109
  output.add_argument(
110
- "-m", "--mode", default=None, choices=("csv", "json", "jsonlines", "line", "line-verbose"), help="Output mode"
110
+ "-m",
111
+ "--mode",
112
+ default=None,
113
+ choices=("csv", "csv-no-header", "json", "jsonlines", "line", "line-verbose"),
114
+ help="Output mode",
111
115
  )
112
116
  output.add_argument(
113
117
  "--split", metavar="COUNT", default=None, type=int, help="Write record files smaller than COUNT records"
@@ -180,6 +184,15 @@ def main(argv: list[str] | None = None) -> int:
180
184
  default=argparse.SUPPRESS,
181
185
  help="Short for --mode=line-verbose",
182
186
  )
187
+ aliases.add_argument(
188
+ "-Cn",
189
+ "--csv-no-header",
190
+ action="store_const",
191
+ const="csv-no-header",
192
+ dest="mode",
193
+ default=argparse.SUPPRESS,
194
+ help="Short for --mode=csv-no-header",
195
+ )
183
196
 
184
197
  args = parser.parse_args(argv)
185
198
 
@@ -198,6 +211,7 @@ def main(argv: list[str] | None = None) -> int:
198
211
  if not args.writer:
199
212
  mode_to_uri = {
200
213
  "csv": "csvfile://",
214
+ "csv-no-header": "csvfile://?header=false",
201
215
  "json": "jsonfile://?indent=2&descriptors=false",
202
216
  "jsonlines": "jsonfile://?descriptors=false",
203
217
  "line": "line://",
@@ -210,7 +224,7 @@ def main(argv: list[str] | None = None) -> int:
210
224
  "format_spec": args.format,
211
225
  }
212
226
  query = urlencode({k: v for k, v in qparams.items() if v})
213
- uri += "&" if urlparse(uri).query else "?" + query
227
+ uri += f"&{query}" if urlparse(uri).query else f"?{query}"
214
228
 
215
229
  if args.split:
216
230
  if not args.writer:
@@ -221,7 +235,7 @@ def main(argv: list[str] | None = None) -> int:
221
235
  query_dict = dict(parse_qsl(parsed.query))
222
236
  query_dict.update({"count": args.split, "suffix-length": args.suffix_length})
223
237
  query = urlencode(query_dict)
224
- uri = parsed.scheme + "://" + parsed.netloc + parsed.path + "?" + query
238
+ uri = f"{parsed.scheme}://{parsed.netloc}{parsed.path}?{query}"
225
239
 
226
240
  record_field_rewriter = None
227
241
  if fields or fields_to_exclude or args.exec_expression:
@@ -117,3 +117,32 @@ class EventHandler:
117
117
  def __call__(self, *args, **kwargs) -> None:
118
118
  for h in self.handlers:
119
119
  h(*args, **kwargs)
120
+
121
+
122
+ def boolean_argument(value: str | bool | int) -> bool:
123
+ """Convert a string, boolean, or integer to a boolean value.
124
+
125
+ This function interprets various string representations of boolean values,
126
+ such as "true", "false", "1", "0", "yes", "no".
127
+ It also accepts boolean and integer values directly.
128
+
129
+ Arguments:
130
+ value: The value to convert. Can be a string, boolean, or integer.
131
+
132
+ Returns:
133
+ bool: The converted boolean value.
134
+
135
+ Raises:
136
+ ValueError: If the value cannot be interpreted as a boolean.
137
+ """
138
+ if isinstance(value, bool):
139
+ return value
140
+ if isinstance(value, int):
141
+ return bool(value)
142
+ if isinstance(value, str):
143
+ value = value.lower()
144
+ if value in ("true", "1", "y", "yes", "on"):
145
+ return True
146
+ if value in ("false", "0", "n", "no", "off"):
147
+ return False
148
+ raise ValueError(f"Invalid boolean argument: {value}")
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '3.21.dev2'
21
- __version_tuple__ = version_tuple = (3, 21, 'dev2')
20
+ __version__ = version = '3.21.dev3'
21
+ __version_tuple__ = version_tuple = (3, 21, 'dev3')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flow.record
3
- Version: 3.21.dev2
3
+ Version: 3.21.dev3
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
@@ -77,6 +77,7 @@ tests/test_regression.py
77
77
  tests/test_selector.py
78
78
  tests/test_splunk_adapter.py
79
79
  tests/test_sqlite_duckdb_adapter.py
80
+ tests/test_utils.py
80
81
  tests/test_xlsx_adapter.py
81
82
  tests/docs/Makefile
82
83
  tests/docs/conf.py
@@ -242,7 +242,7 @@ def test_record_adapter_archive(tmp_path: Path) -> None:
242
242
 
243
243
  # defaults to always archive by /YEAR/MONTH/DAY/ dir structure
244
244
  outdir = tmp_path.joinpath(f"{dt:%Y/%m/%d}")
245
- assert len(list(outdir.iterdir()))
245
+ assert list(outdir.iterdir())
246
246
 
247
247
  # read the archived records and test filename and counts
248
248
  count2 = 0
@@ -8,6 +8,7 @@ import subprocess
8
8
  import sys
9
9
  from datetime import datetime, timezone
10
10
  from io import BytesIO
11
+ from pathlib import Path
11
12
  from typing import Callable
12
13
  from unittest.mock import MagicMock, patch
13
14
 
@@ -691,5 +692,24 @@ def test_record_writer_default_stdout(capsysbinary: pytest.CaptureFixture) -> No
691
692
  assert stdout.startswith(b"\x00\x00\x00\x0f\xc4\rRECORDSTREAM\n")
692
693
 
693
694
 
695
+ def test_rdump_selected_fields(capsysbinary: pytest.CaptureFixture) -> None:
696
+ """Test rdump regression where selected fields was not propagated properly to adapter."""
697
+
698
+ # Pastebin record used for this test
699
+ example_records_json_path = Path(__file__).parent.parent / "examples" / "records.json"
700
+
701
+ # rdump --fields key,title,syntax --csv
702
+ rdump.main([str(example_records_json_path), "--fields", "key,title,syntax", "--csv"])
703
+ captured = capsysbinary.readouterr()
704
+ assert captured.err == b""
705
+ assert captured.out == b"key,title,syntax\r\nQ42eWSaF,A sample pastebin record,text\r\n"
706
+
707
+ # rdump --fields key,title,syntax --csv
708
+ rdump.main([str(example_records_json_path), "--fields", "key,title,syntax", "--csv-no-header"])
709
+ captured = capsysbinary.readouterr()
710
+ assert captured.err == b""
711
+ assert captured.out == b"Q42eWSaF,A sample pastebin record,text\r\n"
712
+
713
+
694
714
  if __name__ == "__main__":
695
715
  __import__("standalone_test").main(globals())
@@ -0,0 +1,25 @@
1
+ import pytest
2
+
3
+ from flow.record.utils import boolean_argument
4
+
5
+
6
+ def test_boolean_argument() -> None:
7
+ assert boolean_argument("True") is True
8
+ assert boolean_argument("true") is True
9
+ assert boolean_argument("trUe") is True
10
+ assert boolean_argument("False") is False
11
+ assert boolean_argument("false") is False
12
+ assert boolean_argument("1") is True
13
+ assert boolean_argument("0") is False
14
+ assert boolean_argument("yes") is True
15
+ assert boolean_argument("no") is False
16
+ assert boolean_argument("y") is True
17
+ assert boolean_argument("n") is False
18
+ assert boolean_argument("on") is True
19
+ assert boolean_argument("off") is False
20
+ assert boolean_argument(True) is True
21
+ assert boolean_argument(False) is False
22
+ assert boolean_argument(1) is True
23
+ assert boolean_argument(0) is False
24
+ with pytest.raises(ValueError, match="Invalid boolean argument: .*"):
25
+ boolean_argument("maybe")
File without changes
File without changes