Skip to content

Commit 86850a2

Browse files
author
Worvast
authored
Merge pull request #297 from boxrhcp/fix/new-line-escape
Add escape new line option for lookups
2 parents 558563e + 3a8c41e commit 86850a2

File tree

7 files changed

+61
-9
lines changed

7 files changed

+61
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [6.0.2] - 2025-03-07
8+
9+
### Added
10+
- Added new --escapenewline or -enl option in lookup creation, edition and deletion. It will escape with
11+
double backslash any new line char found inside a csv field.
12+
713
## [6.0.1] - 2025-02-05
814

915
### Added

devo/sender/lookup.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class Lookup:
4646
DATA_TABLE = "my.lookup.data"
4747
CONTROL_TABLE = "my.lookup.control"
4848

49-
def __init__(self, name="example", historic_tag=None, con=None, delay=5, escape_quotes=False):
49+
def __init__(self, name="example", historic_tag=None, con=None, delay=5, escape_quotes=False, escape_newline=False):
5050
if not re.match(r"^[A-Za-z0-9_]+$", name):
5151
raise Exception(
5252
"Devo Lookup: The name of the lookup is incorrect,"
@@ -63,6 +63,7 @@ def __init__(self, name="example", historic_tag=None, con=None, delay=5, escape_
6363
self.name = name.replace(" ", "_")
6464
self.delay = delay
6565
self.escape_quotes = escape_quotes
66+
self.escape_newline = escape_newline
6667

6768
# Helper methods
6869
# --------------------------------------------------------------------------
@@ -106,7 +107,7 @@ def send_data_line(self, fields=None, delete=False, key_index=None):
106107
:return:
107108
"""
108109
p_fields = Lookup.process_fields(
109-
fields=fields, key_index=key_index, escape_quotes=self.escape_quotes
110+
fields=fields, key_index=key_index, escape_quotes=self.escape_quotes, escape_newline=self.escape_newline
110111
)
111112
self.send_data(row=p_fields, delete=delete)
112113

@@ -242,6 +243,7 @@ def send_csv(
242243
fields=fields,
243244
key_index=key_index,
244245
escape_quotes=self.escape_quotes,
246+
escape_newline=self.escape_newline
245247
)
246248
self.send_data(
247249
row=p_fields,
@@ -258,6 +260,7 @@ def send_csv(
258260
fields=fields,
259261
key_index=key_index,
260262
escape_quotes=self.escape_quotes,
263+
escape_newline=self.escape_newline
261264
)
262265
self.send_data(row=p_fields)
263266

@@ -388,38 +391,41 @@ def list_to_headers(headers=None, key=None, type_of_key="str", key_index=None, t
388391
return out
389392

390393
@staticmethod
391-
def field_to_str(field, escape_quotes=False):
394+
def field_to_str(field, escape_quotes=False, escape_newline=False):
392395
"""
393396
Convert one value to STR, cleaning it
394397
:param field: field to clean
395398
:param escape_quotes: whether to escape quotes in response
399+
:param escape_newline: whether to escape new line char in response
396400
:return:
397401
"""
398-
return ",%s" % Lookup.clean_field(field, escape_quotes)
402+
return ",%s" % Lookup.clean_field(field, escape_quotes, escape_newline)
399403

400404
@staticmethod
401-
def process_fields(fields=None, key_index=None, escape_quotes=False):
405+
def process_fields(fields=None, key_index=None, escape_quotes=False, escape_newline=False):
402406
"""
403407
Method to convert list with one row/fields to STR to send
404408
:param fields: fields list
405409
:param key_index: index of key in fields
406410
:param escape_quotes: whether to escape quotes in response
411+
:param escape_newline: whether to escape new line char in response
407412
:return:
408413
"""
409414
# First the key
410-
out = "%s" % Lookup.clean_field(fields[key_index], escape_quotes)
415+
out = "%s" % Lookup.clean_field(fields[key_index], escape_quotes, escape_newline)
411416

412417
# The rest of the fields
413418
for item in fields[:key_index] + fields[key_index + 1 :]:
414-
out += Lookup.field_to_str(item, escape_quotes)
419+
out += Lookup.field_to_str(item, escape_quotes, escape_newline)
415420
return out
416421

417422
@staticmethod
418-
def clean_field(field=None, escape_quotes=False):
423+
def clean_field(field=None, escape_quotes=False, escape_newline=False):
419424
"""
420425
Strip and quotechar the fields
421426
:param str field: field for clean
422427
:param escape_quotes: whether to escape quotes in response
428+
:param escape_newline: whether to escape new line char in response
423429
:return str: cleaned field
424430
"""
425431
if not isinstance(field, (str, bytes)):
@@ -432,6 +438,9 @@ def clean_field(field=None, escape_quotes=False):
432438
if escape_quotes:
433439
field = field.replace('"', '""')
434440

441+
if escape_newline:
442+
field = field.replace('\n', '\\n')
443+
435444
return '"%s"' % field
436445

437446
@staticmethod

devo/sender/scripts/sender_cli.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,13 @@ def data(**kwargs):
254254
help="Escape Quotes. Default: False",
255255
default=False,
256256
)
257+
@click.option(
258+
"--escapenewline",
259+
"-enl",
260+
is_flag=True,
261+
help="Escape New Line char. Default: False",
262+
default=False,
263+
)
257264
@click.option(
258265
"--no-verify-certificates",
259266
help="Do not Verify certificates credentials before connection",
@@ -279,6 +286,7 @@ def lookup(**kwargs):
279286
historic_tag=None,
280287
con=con,
281288
escape_quotes=config["escapequotes"],
289+
escape_newline=config["escapenewline"]
282290
)
283291

284292
if lookup.check_quotes(config["file"]):

docs/sender/lookup.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,24 @@ To see an example for the lookup configuration please refer to the one shown at
257257
This will escape ALL double quotes in the field value by adding a second double quote to it.
258258

259259
The default behavior is to NOT escape any double quotes.
260+
261+
#### New line chars in the field value
262+
263+
Any new line char inside the field value can cause trouble when creating the lookup if this is not escaped.
264+
An example of this case can be seen below:
265+
266+
```python
267+
lookup.send_data_line(key_index=0, fields=["11", 'new lines\n must be escaped'])
268+
```
269+
270+
That lookup creation will fail because that new line char quote may create null fields. To avoid this, add `"escape_newline": true` to the lookup configuration file and `escape_newline=True` to the `Lookup` constructor. Below, an example for the constructor is shown:
271+
272+
```python
273+
lookup = Lookup(name=self.lookup_name, historic_tag=None, con=con, escape_newline=True)
274+
```
275+
276+
To see an example for the lookup configuration please refer to the one shown at the start of the document.
277+
278+
This will escape ALL new line chars in the field value by adding a second backslash to it.
279+
280+
The default behavior is to NOT escape any new line chars.

tests/integration/resources/testfile_lookup_with_quotes.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ KEY,DATA
22
Key_one,Message_one
33
Key_two,Message_two
44
Key_three,Message_thre
5-
Key_four,Monitor 24" and 19
5+
Key_four,Monitor 24" \nand 19

tests/integration/test_sender_cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ def test_cli_escape_quotes(setup):
386386
"-lk",
387387
"KEY",
388388
"-eq",
389+
"-enl",
389390
"--no-verify-certificates",
390391
],
391392
)

tests/unit/test_sender_number_lookup.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ def test_process_fields_does_not_modify_arguments():
2323
assert fields == ["a", "b", "c"]
2424
assert processed_fields == '"b","a","c"'
2525

26+
def test_process_fields_does_modify_newline():
27+
fields = ["a\ns", "b", "c"]
28+
processed_fields = Lookup.process_fields(fields, key_index=1, escape_newline=True)
29+
30+
assert fields == ["a\ns", "b", "c"]
31+
assert processed_fields == '"b","a\\ns","c"'
32+
2633

2734
# Clean field
2835
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)