From 8dcf141eb65890ed5569137f6504b41508d7df33 Mon Sep 17 00:00:00 2001 From: Stanley Kudrow Date: Fri, 20 Sep 2024 19:07:13 +0300 Subject: [PATCH 1/7] make the utils/parse_dsn() function stricter over validating a URL --- asynch/proto/utils/dsn.py | 16 +++++++++---- tests/test_pool.py | 2 +- tests/test_proto/utils/test_dsn.py | 38 +++++++++++++++++++----------- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/asynch/proto/utils/dsn.py b/asynch/proto/utils/dsn.py index dcf2070..34d1046 100644 --- a/asynch/proto/utils/dsn.py +++ b/asynch/proto/utils/dsn.py @@ -5,6 +5,8 @@ from asynch.proto.models.enums import CompressionAlgorithms, Schemes from asynch.proto.utils.compat import asbool +_SCHEME_SEPARATOR = "://" + _COMPRESSION_ALGORITHMS: set[str] = { CompressionAlgorithms.lz4, CompressionAlgorithms.lz4hc, @@ -18,7 +20,7 @@ class DSNError(Exception): pass -def parse_dsn(dsn: str, *, strict: bool = False) -> dict[str, Any]: +def parse_dsn(dsn: str) -> dict[str, Any]: """Return the client configuration from the given URL. The following URL schemes are supported: @@ -30,7 +32,6 @@ def parse_dsn(dsn: str, *, strict: bool = False) -> dict[str, Any]: - clickhouses://[user:password]@localhost:9440/default :param dsn str: the DSN string - :param strict bool: enable the strict parsing mode :raises DSNError: when parsing fails under the strict mode @@ -38,10 +39,17 @@ def parse_dsn(dsn: str, *, strict: bool = False) -> dict[str, Any]: :rtype: dict[str, Any] """ - scheme, _, _ = dsn.partition("://") - if strict and (scheme not in _SUPPORTED_SCHEMES): + scheme, sep, rest = dsn.partition(_SCHEME_SEPARATOR) + + if not sep: + msg = f"no valid scheme separator in the {dsn}" + raise DSNError(msg) + if scheme not in _SUPPORTED_SCHEMES: msg = f"the scheme {scheme!r} is not in {_SUPPORTED_SCHEMES}" raise DSNError(msg) + if not rest: + msg = f"nothing to parse after the scheme in the {dsn}" + raise DSNError(msg) settings = {} kwargs = {} diff --git a/tests/test_pool.py b/tests/test_pool.py index e6b862e..1bed0e5 100644 --- a/tests/test_pool.py +++ b/tests/test_pool.py @@ -4,7 +4,7 @@ import pytest from asynch.connection import Connection -from asynch.pool import Pool, AsynchPoolError +from asynch.pool import AsynchPoolError, Pool from asynch.proto import constants from asynch.proto.models.enums import PoolStatuses diff --git a/tests/test_proto/utils/test_dsn.py b/tests/test_proto/utils/test_dsn.py index 6292adb..521cf71 100644 --- a/tests/test_proto/utils/test_dsn.py +++ b/tests/test_proto/utils/test_dsn.py @@ -9,16 +9,14 @@ @pytest.mark.parametrize( - ("dsn", "strict", "ctx", "answer"), + ("dsn", "ctx", "answer"), [ - ("", False, does_not_raise(), {}), - ("some_scheme://", False, does_not_raise(), {"database": "some_scheme:/"}), - ("some_scheme://", True, pytest.raises(DSNError), None), - (f"{Schemes.clickhouse}://", True, does_not_raise(), {}), - (f"{Schemes.clickhouses}://", True, does_not_raise(), {"secure": True}), + ("", pytest.raises(DSNError), None), + ("some_scheme://", pytest.raises(DSNError), None), + (f"{Schemes.clickhouse}://", pytest.raises(DSNError), None), + (f"{Schemes.clickhouses}://", pytest.raises(DSNError), None), ( f"{Schemes.clickhouse}://ch@lochost/", - False, does_not_raise(), { "user": "ch", @@ -27,7 +25,6 @@ ), ( f"{Schemes.clickhouse}://ch:pwd@lochost/", - False, does_not_raise(), { "user": "ch", @@ -37,7 +34,6 @@ ), ( f"{Schemes.clickhouse}://ch@lochost:4321/", - False, does_not_raise(), { "user": "ch", @@ -47,7 +43,6 @@ ), ( f"{Schemes.clickhouse}://ch:pwd@lochost:1234/db", - False, does_not_raise(), { "user": "ch", @@ -57,13 +52,28 @@ "database": "db", }, ), + ( + "lochost:1234/test", + pytest.raises(DSNError), + None, + ), + ( + f"{Schemes.clickhouse}://lochost:1234/test", + does_not_raise(), + {"host": "lochost", "port": 1234, "database": "test"}, + ), + ( + f"{Schemes.clickhouse} :// lochost : 1234 / test", + pytest.raises(DSNError), + None, + ), ], ) def test_dsn_basic_credentials( - dsn: str, strict: bool, ctx: Optional[ContextManager], answer: Optional[dict[str, Any]] + dsn: str, ctx: Optional[ContextManager], answer: Optional[dict[str, Any]] ) -> None: with ctx: - result = parse_dsn(dsn, strict=strict) + result = parse_dsn(dsn=dsn) assert result == answer @@ -82,7 +92,7 @@ def test_dsn_basic_credentials( }, ), ( - "clickhouse://ch:pwd@loc:2938/ault", + f"{Schemes.clickhouse}://ch:pwd@loc:2938/ault", ( "verify=true" "&secure=yes" @@ -115,7 +125,7 @@ def test_dsn_with_query_fragments( answer: dict[str, Any], ) -> None: url = f"{dsn}?{query}" - config = parse_dsn(url, strict=True) + config = parse_dsn(dsn=url) for key, value in answer.items(): if value is None: From 2a8621d294381cd195864d5ae8f385e2055d47a0 Mon Sep 17 00:00:00 2001 From: Stanley Kudrow Date: Fri, 20 Sep 2024 19:09:59 +0300 Subject: [PATCH 2/7] add this PR record in the CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4af0268..ca1d550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### 0.2.5 +- Remove the `strict` keyword from the `utils/parse_dsn` function. Add more validation rules in the `parse_dsn` function. By @stankudrow in #XXX - Reconsider the API of the `Connection`, `Cursor` and `Pool` classes and deprecate outdated methods or properties. Define the DB-API v2.0 compliant exception hierarchy. Update project dependencies and metadata. By @stankudrow in #111. - Fix infinite iteration case when a cursor object is put in the `async for` loop. By @stankudrow in #112. - Fix pool connection management (the discussion #108 by @DFilyushin) by @stankudrow in #109: From f87dff1fe5f9192d0322f7bd084b5090f478d9d6 Mon Sep 17 00:00:00 2001 From: Stanley Kudrow Date: Fri, 20 Sep 2024 19:18:27 +0300 Subject: [PATCH 3/7] add the PR #113 record on parse_dsn function fixes --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca1d550..cdc85c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### 0.2.5 -- Remove the `strict` keyword from the `utils/parse_dsn` function. Add more validation rules in the `parse_dsn` function. By @stankudrow in #XXX +- Remove the `strict` keyword from the `utils/parse_dsn` function. Add more validation rules in the `parse_dsn` function. By @stankudrow in #113 - Reconsider the API of the `Connection`, `Cursor` and `Pool` classes and deprecate outdated methods or properties. Define the DB-API v2.0 compliant exception hierarchy. Update project dependencies and metadata. By @stankudrow in #111. - Fix infinite iteration case when a cursor object is put in the `async for` loop. By @stankudrow in #112. - Fix pool connection management (the discussion #108 by @DFilyushin) by @stankudrow in #109: From ff980e09bdbd2ed75772421652646114079cfacf Mon Sep 17 00:00:00 2001 From: Stanley Kudrow Date: Sat, 21 Sep 2024 20:15:17 +0300 Subject: [PATCH 4/7] update the PR #113 record in the CHANGELOG.md file --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdc85c1..f4971e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### 0.2.5 -- Remove the `strict` keyword from the `utils/parse_dsn` function. Add more validation rules in the `parse_dsn` function. By @stankudrow in #113 +- Add more validation rules in the `parse_dsn` function. By @stankudrow in #113 - Reconsider the API of the `Connection`, `Cursor` and `Pool` classes and deprecate outdated methods or properties. Define the DB-API v2.0 compliant exception hierarchy. Update project dependencies and metadata. By @stankudrow in #111. - Fix infinite iteration case when a cursor object is put in the `async for` loop. By @stankudrow in #112. - Fix pool connection management (the discussion #108 by @DFilyushin) by @stankudrow in #109: From 2faf8368e195f7426cd63cffc3f2ad972796ef7f Mon Sep 17 00:00:00 2001 From: Stanley Kudrow Date: Sat, 21 Sep 2024 21:09:08 +0300 Subject: [PATCH 5/7] mention the discussion #100 for the PR #112 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4971e5..630d5dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Add more validation rules in the `parse_dsn` function. By @stankudrow in #113 - Reconsider the API of the `Connection`, `Cursor` and `Pool` classes and deprecate outdated methods or properties. Define the DB-API v2.0 compliant exception hierarchy. Update project dependencies and metadata. By @stankudrow in #111. -- Fix infinite iteration case when a cursor object is put in the `async for` loop. By @stankudrow in #112. +- Fix infinite iteration case when a cursor object is put in the `async for` loop (the discussion #100 by @KuzenkovAG). By @stankudrow in #112. - Fix pool connection management (the discussion #108 by @DFilyushin) by @stankudrow in #109: - add the asynchronous context manager support to the `Pool` class with the pool "startup()" as `__aenter__` and "shutdown()" as `__aexit__` methods; From 1062cc7da0c06175fb7d0e9527902ff8554639e8 Mon Sep 17 00:00:00 2001 From: Stanley Kudrow Date: Mon, 23 Sep 2024 10:31:12 +0300 Subject: [PATCH 6/7] weaken deprecations over the Connection echo parametre --- asynch/connection.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/asynch/connection.py b/asynch/connection.py index 81177f0..806e203 100644 --- a/asynch/connection.py +++ b/asynch/connection.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Any, Optional from warnings import warn from asynch.cursors import Cursor @@ -56,7 +56,8 @@ def __init__( warn( ( "The `echo` flag in the constructor is deprecated since the v0.2.5. " - "This flag is specifiable in the `cursor` method of the Connection class. " + "The flag can be specified within the connection keyword arguments (`kwargs`). " + "The `echo` flag can be overriden in the `cursor` method of the Connection. " "The `echo` parameter may be removed in the version 0.2.6 or later." ), DeprecationWarning, @@ -177,13 +178,23 @@ def database(self) -> str: def echo(self) -> bool: warn( ( - "The `echo` parameter should be specified in the `cursor` method." - "The property may be removed in the version 0.2.6 or later." + "The `echo` property may be removed in the version 0.2.6 or later. " + "The `echo` parametre can be specified in the Connection `kwargs` settings." ), DeprecationWarning, ) return self._echo + @property + def settings(self) -> dict[str, Any]: + """Return the settings for a Connection object. + + :return: Connection initial settings + :rtype: dict[str, Any] + """ + + return self._connection_kwargs + async def close(self) -> None: """Close the connection.""" @@ -215,21 +226,13 @@ def cursor(self, cursor: Optional[Cursor] = None, *, echo: bool = False) -> Curs set to True even if the `self.echo` property returns False. :param cursor Optional[Cursor]: Cursor factory class - :param echo bool: + :param echo bool: to override the `Connection.echo` parametre for a cursor :return: the cursor object of a connection :rtype: Cursor """ cursor_cls = cursor or self._cursor_cls - warn( - ( - "When `echo` parameter is set to False (by default), " - "the deprecated `self.echo` property is in effect ." - "This behaviour may be removed in the version 0.2.6 or later." - ), - UserWarning, - ) return cursor_cls(self, echo or self.echo) async def ping(self) -> None: @@ -267,6 +270,7 @@ async def connect( The `echo` parameter is deprecated since the version 0.2.5. It may be removed in the version 0.2.6 or later. + You can specify this parametre in the `kwargs`. :param dsn str: DSN/connection string (if None -> constructed from default dsn parts) :param user str: user string ("default" by default) From f81113641d9b2ae108de17b7ad6d1c331cdc9dfa Mon Sep 17 00:00:00 2001 From: Stanley Kudrow Date: Mon, 23 Sep 2024 10:39:01 +0300 Subject: [PATCH 7/7] remove the deprecation of the Connection.echo parametre --- asynch/connection.py | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/asynch/connection.py b/asynch/connection.py index 806e203..a9de749 100644 --- a/asynch/connection.py +++ b/asynch/connection.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Optional from warnings import warn from asynch.cursors import Cursor @@ -53,15 +53,6 @@ def __init__( self._closed: Optional[bool] = None self._cursor_cls = cursor_cls self._connection_kwargs = kwargs - warn( - ( - "The `echo` flag in the constructor is deprecated since the v0.2.5. " - "The flag can be specified within the connection keyword arguments (`kwargs`). " - "The `echo` flag can be overriden in the `cursor` method of the Connection. " - "The `echo` parameter may be removed in the version 0.2.6 or later." - ), - DeprecationWarning, - ) self._echo = echo async def __aenter__(self) -> "Connection": @@ -176,25 +167,8 @@ def database(self) -> str: @property def echo(self) -> bool: - warn( - ( - "The `echo` property may be removed in the version 0.2.6 or later. " - "The `echo` parametre can be specified in the Connection `kwargs` settings." - ), - DeprecationWarning, - ) return self._echo - @property - def settings(self) -> dict[str, Any]: - """Return the settings for a Connection object. - - :return: Connection initial settings - :rtype: dict[str, Any] - """ - - return self._connection_kwargs - async def close(self) -> None: """Close the connection.""" @@ -268,10 +242,6 @@ async def connect( When the connection is no longer needed, consider `await`ing the `conn.close()` method. - The `echo` parameter is deprecated since the version 0.2.5. - It may be removed in the version 0.2.6 or later. - You can specify this parametre in the `kwargs`. - :param dsn str: DSN/connection string (if None -> constructed from default dsn parts) :param user str: user string ("default" by default) :param password str: password string ("" by default)