From e552b842e3f096a58f9e7ba80611530b29468ad1 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 8 Feb 2023 11:05:08 +0200 Subject: [PATCH 01/11] implement support for bytea type --- src/firebolt/async_db/_types.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/firebolt/async_db/_types.py b/src/firebolt/async_db/_types.py index 73f2c109ede..ef3406743ed 100644 --- a/src/firebolt/async_db/_types.py +++ b/src/firebolt/async_db/_types.py @@ -51,9 +51,9 @@ def parse_datetime(datetime_string: str) -> datetime: _NoneType = type(None) _col_types = (int, float, str, datetime, date, bool, list, Decimal, _NoneType) # duplicating this since 3.7 can't unpack Union -ColType = Union[int, float, str, datetime, date, bool, list, Decimal, _NoneType] +ColType = Union[int, float, str, datetime, date, bool, list, Decimal, _NoneType, bytes] RawColType = Union[int, float, str, bool, list, _NoneType] -ParameterType = Union[int, float, str, datetime, date, bool, Decimal, Sequence] +ParameterType = Union[int, float, str, datetime, date, bool, Decimal, Sequence, bytes] # These definitions are required by PEP-249 Date = date @@ -78,12 +78,13 @@ def TimeFromTicks(t: int) -> None: TimestampFromTicks = datetime.fromtimestamp -def Binary(value: str) -> str: - """Convert string to binary for Firebolt DB does nothing.""" - return value +def Binary(value: str) -> bytes: + """Encode a string into UTF-8.""" + return value.encode("utf-8") -STRING = BINARY = str +STRING = str +BINARY = bytes NUMBER = int DATETIME = datetime ROWID = int @@ -169,6 +170,8 @@ class _InternalType(Enum): Boolean = "boolean" + Bytea = "bytea" + Nothing = "Nothing" @cached_property @@ -188,6 +191,7 @@ def python_type(self) -> type: _InternalType.TimestampNtz: datetime, _InternalType.TimestampTz: datetime, _InternalType.Boolean: bool, + _InternalType.Bytea: bytes, # For simplicity, this could happen only during 'select null' query _InternalType.Nothing: str, } @@ -221,6 +225,13 @@ def parse_type(raw_type: str) -> Union[type, ARRAY, DECIMAL]: # noqa: C901 return str +BYTEA_PREFIX = "\\\\x" + + +def _parse_bytea(str_value: str) -> bytes: + return bytes.fromhex(str_value[len(BYTEA_PREFIX) :]) + + def parse_value( value: RawColType, ctype: Union[type, ARRAY, DECIMAL], @@ -244,6 +255,10 @@ def parse_value( if not isinstance(value, (bool, int)): raise DataError(f"Invalid boolean value {value}: bool or int expected") return bool(value) + if ctype is bytes: + if not isinstance(value, str): + raise DataError(f"Invalid bytea value {value}: str expected") + return _parse_bytea(value) if isinstance(ctype, DECIMAL): assert isinstance(value, (str, int)) return Decimal(value) @@ -274,6 +289,9 @@ def format_value(value: ParameterType) -> str: return f"'{value.strftime('%Y-%m-%d %H:%M:%S')}'" elif isinstance(value, date): return f"'{value.isoformat()}'" + elif isinstance(value, bytes): + # Encode each byte into hex + return "".join(f"\\x{b:02x}" for b in value) if value is None: return "NULL" elif isinstance(value, Sequence): From 93452800552aac81a7c18ada3e96a3545cbc1fe5 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 8 Feb 2023 11:24:43 +0200 Subject: [PATCH 02/11] add unit tests --- src/firebolt/async_db/_types.py | 2 +- tests/unit/async_db/conftest.py | 1 + tests/unit/async_db/test_typing_parse.py | 15 +++++++++++++++ tests/unit/db_conftest.py | 4 ++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/firebolt/async_db/_types.py b/src/firebolt/async_db/_types.py index ef3406743ed..29ac9989520 100644 --- a/src/firebolt/async_db/_types.py +++ b/src/firebolt/async_db/_types.py @@ -49,7 +49,7 @@ def parse_datetime(datetime_string: str) -> datetime: from firebolt.utils.util import cached_property _NoneType = type(None) -_col_types = (int, float, str, datetime, date, bool, list, Decimal, _NoneType) +_col_types = (int, float, str, datetime, date, bool, list, Decimal, _NoneType, bytes) # duplicating this since 3.7 can't unpack Union ColType = Union[int, float, str, datetime, date, bool, list, Decimal, _NoneType, bytes] RawColType = Union[int, float, str, bool, list, _NoneType] diff --git a/tests/unit/async_db/conftest.py b/tests/unit/async_db/conftest.py index d7064bfa691..fdca43ab438 100644 --- a/tests/unit/async_db/conftest.py +++ b/tests/unit/async_db/conftest.py @@ -51,6 +51,7 @@ def types_map() -> Dict[str, type]: "Decimal(38)": str, "boolean": bool, "SomeRandomNotExistingType": str, + "bytea": bytes, } array_types = {f"array({k})": ARRAY(v) for k, v in base_types.items()} nullable_arrays = {f"{k} null": v for k, v in array_types.items()} diff --git a/tests/unit/async_db/test_typing_parse.py b/tests/unit/async_db/test_typing_parse.py index 76921d2101d..d8f67f4eb8d 100644 --- a/tests/unit/async_db/test_typing_parse.py +++ b/tests/unit/async_db/test_typing_parse.py @@ -248,3 +248,18 @@ def test_parse_value_bool() -> None: with raises(DataError): parse_value("true", bool) + + +def test_parse_value_bytes() -> None: + """parse_value parses all int values correctly.""" + assert ( + parse_value("\\\\x616263", bytes) == b"abc" + ), "Error parsing bytes: provided str" + assert parse_value(None, bytes) is None, "Error parsing bytes: provided None" + + with raises(ValueError): + parse_value("\\\\xabc", bytes) + + for val in (1, True, Exception()): + with raises(DataError): + parse_value(val, bytes) diff --git a/tests/unit/db_conftest.py b/tests/unit/db_conftest.py index 2c7505370f7..d1cd3bac4ca 100644 --- a/tests/unit/db_conftest.py +++ b/tests/unit/db_conftest.py @@ -32,6 +32,7 @@ def query_description() -> List[Column]: Column("bool", "boolean", None, None, None, None, None), Column("array", "array(int)", None, None, None, None, None), Column("decimal", "Decimal(12, 34)", None, None, None, None, None), + Column("bytea", "bytea", None, None, None, None, None), ] @@ -54,6 +55,7 @@ def python_query_description() -> List[Column]: Column("bool", bool, None, None, None, None, None), Column("array", ARRAY(int), None, None, None, None, None), Column("decimal", DECIMAL(12, 34), None, None, None, None, None), + Column("bytea", bytes, None, None, None, None, None), ] @@ -77,6 +79,7 @@ def query_data() -> List[List[ColType]]: 1, [1, 2, 3, 4], "123456789.123456789123456789123456789", + "\\\\x616263", ] for i in range(QUERY_ROW_COUNT) ] @@ -102,6 +105,7 @@ def python_query_data() -> List[List[ColType]]: 1, [1, 2, 3, 4], Decimal("123456789.123456789123456789123456789"), + b"abc", ] for i in range(QUERY_ROW_COUNT) ] From 36ac75adb80e644074d9d2b3341073d40ee6145e Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 8 Feb 2023 11:44:01 +0200 Subject: [PATCH 03/11] add integration tests, extend unit tests --- src/firebolt/async_db/_types.py | 4 +-- .../dbapi/async/test_queries_async.py | 32 ++++++++++++++++++- tests/integration/dbapi/conftest.py | 3 ++ tests/integration/dbapi/sync/test_queries.py | 23 +++++++++++++ tests/unit/async_db/test_typing_format.py | 13 +++++++- tests/unit/async_db/test_typing_parse.py | 4 +-- tests/unit/db_conftest.py | 2 +- 7 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/firebolt/async_db/_types.py b/src/firebolt/async_db/_types.py index 29ac9989520..37fc4c8cbd8 100644 --- a/src/firebolt/async_db/_types.py +++ b/src/firebolt/async_db/_types.py @@ -225,7 +225,7 @@ def parse_type(raw_type: str) -> Union[type, ARRAY, DECIMAL]: # noqa: C901 return str -BYTEA_PREFIX = "\\\\x" +BYTEA_PREFIX = "\\x" def _parse_bytea(str_value: str) -> bytes: @@ -291,7 +291,7 @@ def format_value(value: ParameterType) -> str: return f"'{value.isoformat()}'" elif isinstance(value, bytes): # Encode each byte into hex - return "".join(f"\\x{b:02x}" for b in value) + return "'" + "".join(f"\\x{b:02x}" for b in value) + "'" if value is None: return "NULL" elif isinstance(value, Sequence): diff --git a/tests/integration/dbapi/async/test_queries_async.py b/tests/integration/dbapi/async/test_queries_async.py index f1ef2f8746f..cc8b9f43cf1 100644 --- a/tests/integration/dbapi/async/test_queries_async.py +++ b/tests/integration/dbapi/async/test_queries_async.py @@ -4,7 +4,13 @@ from pytest import mark, raises -from firebolt.async_db import Connection, Cursor, DataError, OperationalError +from firebolt.async_db import ( + Binary, + Connection, + Cursor, + DataError, + OperationalError, +) from firebolt.async_db._types import ColType, Column from firebolt.async_db.cursor import QueryStatus @@ -487,3 +493,27 @@ async def test_server_side_async_execution_get_status( # assert ( # type(status) is QueryStatus, # ), "get_status() did not return a QueryStatus object." + + +async def test_bytea_roundtrip( + connection: Connection, +) -> None: + """Inserted and than selected bytea value doesn't get corrupted.""" + with connection.cursor() as c: + await c.execute("DROP TABLE IF EXISTS test_bytea_roundtrip") + await c.execute( + "CREATE FACT TABLE test_bytea_roundtrip(id int, b bytea) primary index id" + ) + + data = "bytea_123" + + await c.execute( + "INSERT INTO test_bytea_roundtrip VALUES (1, ?)", (Binary(data),) + ) + await c.execute("SELECT b FROM test_bytea_roundtrip") + + bytes_data = (await c.fetchone())[0] + + assert ( + bytes_data.decode("utf-8") == data + ), "Invalid bytea data returned after roundtrip" diff --git a/tests/integration/dbapi/conftest.py b/tests/integration/dbapi/conftest.py index 657449e40d1..3a62869d4e2 100644 --- a/tests/integration/dbapi/conftest.py +++ b/tests/integration/dbapi/conftest.py @@ -76,6 +76,7 @@ def all_types_query() -> str: "[1,2,3,4] as \"array\", cast('1231232.123459999990457054844258706536' as " 'decimal(38,30)) as "decimal", ' 'cast(null as int) as "nullable"' + "'abc123'::bytea as bytea" ) @@ -104,6 +105,7 @@ def all_types_query_description() -> List[Column]: Column("array", ARRAY(int), None, None, None, None, None), Column("decimal", DECIMAL(38, 30), None, None, None, None, None), Column("nullable", int, None, None, None, None, None), + Column("bytea", bytes, None, None, None, None, None), ] @@ -142,6 +144,7 @@ def all_types_query_response(timezone_offset_seconds: int) -> List[ColType]: [1, 2, 3, 4], Decimal("1231232.123459999990457054844258706536"), None, + b"abc123", ] ] diff --git a/tests/integration/dbapi/sync/test_queries.py b/tests/integration/dbapi/sync/test_queries.py index f29946602f7..3b0abd22a1a 100644 --- a/tests/integration/dbapi/sync/test_queries.py +++ b/tests/integration/dbapi/sync/test_queries.py @@ -9,6 +9,7 @@ from firebolt.async_db.cursor import QueryStatus from firebolt.client.auth import Auth from firebolt.db import ( + Binary, Connection, Cursor, DataError, @@ -519,3 +520,25 @@ def run_query(): connection.close() assert not exceptions + + +def test_bytea_roundtrip( + connection: Connection, +) -> None: + """Inserted and than selected bytea value doesn't get corrupted.""" + with connection.cursor() as c: + c.execute("DROP TABLE IF EXISTS test_bytea_roundtrip") + c.execute( + "CREATE FACT TABLE test_bytea_roundtrip(id int, b bytea) primary index id" + ) + + data = "bytea_123" + + c.execute("INSERT INTO test_bytea_roundtrip VALUES (1, ?)", (Binary(data),)) + c.execute("SELECT b FROM test_bytea_roundtrip") + + bytes_data = (c.fetchone())[0] + + assert ( + bytes_data.decode("utf-8") == data + ), "Invalid bytea data returned after roundtrip" diff --git a/tests/unit/async_db/test_typing_format.py b/tests/unit/async_db/test_typing_format.py index 97bd835b22f..4027b5dd1ff 100644 --- a/tests/unit/async_db/test_typing_format.py +++ b/tests/unit/async_db/test_typing_format.py @@ -6,7 +6,12 @@ from sqlparse import parse from sqlparse.sql import Statement -from firebolt.async_db import DataError, InterfaceError, NotSupportedError +from firebolt.async_db import ( + Binary, + DataError, + InterfaceError, + NotSupportedError, +) from firebolt.async_db._types import ( SetParameter, format_statement, @@ -44,6 +49,8 @@ (("a", "b", "c"), "['a', 'b', 'c']"), # None (None, "NULL"), + # Bytea + (b"abc", "'\\x61\\x62\\x63'"), ], ) def test_format_value(value: str, result: str) -> None: @@ -188,3 +195,7 @@ def test_statement_to_set(statement: Statement, result: Optional[SetParameter]) def test_statement_to_set_errors(statement: Statement, error: Exception) -> None: with raises(error): statement_to_set(statement) + + +def test_binary() -> None: + assert Binary("abc") == b"abc" diff --git a/tests/unit/async_db/test_typing_parse.py b/tests/unit/async_db/test_typing_parse.py index d8f67f4eb8d..a75ca92ad8c 100644 --- a/tests/unit/async_db/test_typing_parse.py +++ b/tests/unit/async_db/test_typing_parse.py @@ -253,12 +253,12 @@ def test_parse_value_bool() -> None: def test_parse_value_bytes() -> None: """parse_value parses all int values correctly.""" assert ( - parse_value("\\\\x616263", bytes) == b"abc" + parse_value("\\x616263", bytes) == b"abc" ), "Error parsing bytes: provided str" assert parse_value(None, bytes) is None, "Error parsing bytes: provided None" with raises(ValueError): - parse_value("\\\\xabc", bytes) + parse_value("\\xabc", bytes) for val in (1, True, Exception()): with raises(DataError): diff --git a/tests/unit/db_conftest.py b/tests/unit/db_conftest.py index d1cd3bac4ca..b9dccba3cf8 100644 --- a/tests/unit/db_conftest.py +++ b/tests/unit/db_conftest.py @@ -79,7 +79,7 @@ def query_data() -> List[List[ColType]]: 1, [1, 2, 3, 4], "123456789.123456789123456789123456789", - "\\\\x616263", + "\\x616263", ] for i in range(QUERY_ROW_COUNT) ] From 645fba473fa990fc300fda739d24be8205df73cb Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 1 Mar 2023 10:33:27 +0200 Subject: [PATCH 04/11] add prefix validation --- src/firebolt/async_db/_types.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/firebolt/async_db/_types.py b/src/firebolt/async_db/_types.py index 37fc4c8cbd8..830ef074dd3 100644 --- a/src/firebolt/async_db/_types.py +++ b/src/firebolt/async_db/_types.py @@ -229,6 +229,8 @@ def parse_type(raw_type: str) -> Union[type, ARRAY, DECIMAL]: # noqa: C901 def _parse_bytea(str_value: str) -> bytes: + if len(str_value) < len(BYTEA_PREFIX) or str_value[:len(BYTEA_PREFIX)] != BYTEA_PREFIX: + raise ValueError(f"Invalid bytea value format: {BYTEA_PREFIX} prefix expected") return bytes.fromhex(str_value[len(BYTEA_PREFIX) :]) From 3f87e27cb96455069355727ed973028fc2a34390 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 1 Mar 2023 10:36:26 +0200 Subject: [PATCH 05/11] extend unit tests --- tests/unit/async_db/test_typing_parse.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/async_db/test_typing_parse.py b/tests/unit/async_db/test_typing_parse.py index a75ca92ad8c..fad2c93a693 100644 --- a/tests/unit/async_db/test_typing_parse.py +++ b/tests/unit/async_db/test_typing_parse.py @@ -260,6 +260,10 @@ def test_parse_value_bytes() -> None: with raises(ValueError): parse_value("\\xabc", bytes) + # Missing prefix + with raises(ValueError): + parse_value("616263", bytes) + for val in (1, True, Exception()): with raises(DataError): parse_value(val, bytes) From cabe11b2653f422fa40e09a3ce744a38e8a7f353 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 1 Mar 2023 10:38:05 +0200 Subject: [PATCH 06/11] extend integration tests --- tests/integration/dbapi/async/test_queries_async.py | 2 +- tests/integration/dbapi/conftest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/dbapi/async/test_queries_async.py b/tests/integration/dbapi/async/test_queries_async.py index cc8b9f43cf1..5271f4f4e59 100644 --- a/tests/integration/dbapi/async/test_queries_async.py +++ b/tests/integration/dbapi/async/test_queries_async.py @@ -505,7 +505,7 @@ async def test_bytea_roundtrip( "CREATE FACT TABLE test_bytea_roundtrip(id int, b bytea) primary index id" ) - data = "bytea_123" + data = "bytea_123\n\t\\\0ヽ༼ຈل͜ຈ༽ノ" await c.execute( "INSERT INTO test_bytea_roundtrip VALUES (1, ?)", (Binary(data),) diff --git a/tests/integration/dbapi/conftest.py b/tests/integration/dbapi/conftest.py index 3a62869d4e2..313ef16fb4f 100644 --- a/tests/integration/dbapi/conftest.py +++ b/tests/integration/dbapi/conftest.py @@ -76,7 +76,7 @@ def all_types_query() -> str: "[1,2,3,4] as \"array\", cast('1231232.123459999990457054844258706536' as " 'decimal(38,30)) as "decimal", ' 'cast(null as int) as "nullable"' - "'abc123'::bytea as bytea" + "'abc123'::bytea as \"bytea\"" ) From 3c3f1c832d9d993240f7266c4f9eb20085250414 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 1 Mar 2023 11:40:15 +0200 Subject: [PATCH 07/11] fix integration tests --- tests/integration/dbapi/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/dbapi/conftest.py b/tests/integration/dbapi/conftest.py index 313ef16fb4f..1843a22b894 100644 --- a/tests/integration/dbapi/conftest.py +++ b/tests/integration/dbapi/conftest.py @@ -75,7 +75,7 @@ def all_types_query() -> str: 'true as "boolean", ' "[1,2,3,4] as \"array\", cast('1231232.123459999990457054844258706536' as " 'decimal(38,30)) as "decimal", ' - 'cast(null as int) as "nullable"' + 'cast(null as int) as "nullable", ' "'abc123'::bytea as \"bytea\"" ) From 02ffc72b14d5fe3dd250dc78e3421066b2716c77 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 1 Mar 2023 14:27:50 +0200 Subject: [PATCH 08/11] remove \ character from test --- tests/integration/dbapi/async/test_queries_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/dbapi/async/test_queries_async.py b/tests/integration/dbapi/async/test_queries_async.py index 5271f4f4e59..8f0e641f2b6 100644 --- a/tests/integration/dbapi/async/test_queries_async.py +++ b/tests/integration/dbapi/async/test_queries_async.py @@ -505,7 +505,7 @@ async def test_bytea_roundtrip( "CREATE FACT TABLE test_bytea_roundtrip(id int, b bytea) primary index id" ) - data = "bytea_123\n\t\\\0ヽ༼ຈل͜ຈ༽ノ" + data = "bytea_123\n\t\\ヽ༼ຈل͜ຈ༽ノ" await c.execute( "INSERT INTO test_bytea_roundtrip VALUES (1, ?)", (Binary(data),) From c9d81d1264694b289be2ec8095d102edc2905256 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Wed, 1 Mar 2023 14:28:27 +0200 Subject: [PATCH 09/11] extend sync tests --- tests/integration/dbapi/sync/test_queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/dbapi/sync/test_queries.py b/tests/integration/dbapi/sync/test_queries.py index 3b0abd22a1a..0186bc1b03b 100644 --- a/tests/integration/dbapi/sync/test_queries.py +++ b/tests/integration/dbapi/sync/test_queries.py @@ -532,7 +532,7 @@ def test_bytea_roundtrip( "CREATE FACT TABLE test_bytea_roundtrip(id int, b bytea) primary index id" ) - data = "bytea_123" + data = "bytea_123\n\t\\ヽ༼ຈل͜ຈ༽ノ" c.execute("INSERT INTO test_bytea_roundtrip VALUES (1, ?)", (Binary(data),)) c.execute("SELECT b FROM test_bytea_roundtrip") From e885de3b7a44e96b09335764644ce24c07a78651 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Thu, 2 Mar 2023 10:20:23 +0200 Subject: [PATCH 10/11] remove backslash since it's not supported --- tests/integration/dbapi/async/test_queries_async.py | 2 +- tests/integration/dbapi/sync/test_queries.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/dbapi/async/test_queries_async.py b/tests/integration/dbapi/async/test_queries_async.py index 8f0e641f2b6..7de7f7ef56d 100644 --- a/tests/integration/dbapi/async/test_queries_async.py +++ b/tests/integration/dbapi/async/test_queries_async.py @@ -505,7 +505,7 @@ async def test_bytea_roundtrip( "CREATE FACT TABLE test_bytea_roundtrip(id int, b bytea) primary index id" ) - data = "bytea_123\n\t\\ヽ༼ຈل͜ຈ༽ノ" + data = "bytea_123\n\tヽ༼ຈل͜ຈ༽ノ" await c.execute( "INSERT INTO test_bytea_roundtrip VALUES (1, ?)", (Binary(data),) diff --git a/tests/integration/dbapi/sync/test_queries.py b/tests/integration/dbapi/sync/test_queries.py index 0186bc1b03b..5d3084c94d5 100644 --- a/tests/integration/dbapi/sync/test_queries.py +++ b/tests/integration/dbapi/sync/test_queries.py @@ -532,7 +532,7 @@ def test_bytea_roundtrip( "CREATE FACT TABLE test_bytea_roundtrip(id int, b bytea) primary index id" ) - data = "bytea_123\n\t\\ヽ༼ຈل͜ຈ༽ノ" + data = "bytea_123\n\tヽ༼ຈل͜ຈ༽ノ" c.execute("INSERT INTO test_bytea_roundtrip VALUES (1, ?)", (Binary(data),)) c.execute("SELECT b FROM test_bytea_roundtrip") From 5f2660efcd6fffa1107d89b897db8e98edd66ec3 Mon Sep 17 00:00:00 2001 From: Stepan Burlakov Date: Fri, 10 Mar 2023 11:44:08 +0200 Subject: [PATCH 11/11] fix formatting --- src/firebolt/async_db/_types.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/firebolt/async_db/_types.py b/src/firebolt/async_db/_types.py index 830ef074dd3..7a3d17bd2a7 100644 --- a/src/firebolt/async_db/_types.py +++ b/src/firebolt/async_db/_types.py @@ -229,7 +229,10 @@ def parse_type(raw_type: str) -> Union[type, ARRAY, DECIMAL]: # noqa: C901 def _parse_bytea(str_value: str) -> bytes: - if len(str_value) < len(BYTEA_PREFIX) or str_value[:len(BYTEA_PREFIX)] != BYTEA_PREFIX: + if ( + len(str_value) < len(BYTEA_PREFIX) + or str_value[: len(BYTEA_PREFIX)] != BYTEA_PREFIX + ): raise ValueError(f"Invalid bytea value format: {BYTEA_PREFIX} prefix expected") return bytes.fromhex(str_value[len(BYTEA_PREFIX) :])