Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reconsider the Connection and Pool classes APIs -> preparing v0.3.0 release #130

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

### 0.3.0

- Update the `Connection` and `Pool` classes API. By @stankudrow in #130:
- remove the deprecated `connected` property from the `Connection` class
- fix type hinting for `Cursor` class as incoming parameter for the connection `cursor` method
- make the connection `close` async method more consistent
- turn the `async/connection.py:connect` function into the async context manager
- get rid of inheritance from the `asyncio.AbstractServer` for the `Pool` class (mypy got satisfied)
- check the freshness of a connection before giving it from a pool. Inspired by the issue #127 from @nils-borrmann-tacto.
- mutate the `create_pool` function into the async context manager
- Add `mypy` dependency into the `lint` section. By @stankudrow in #128
- Gracefully handle connections terminated by the server. By @nils-borrmann-tacto in #129.
- Remove the deprecated API from `cursor.py` module. By @stankudrow in #125.
Expand Down
76 changes: 72 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ If you want to install [`clickhouse-cityhash`](https://pypi.org/project/clickhou
> pip install asynch[compression]
```

## Release v0.3.0 announcement

The version 0.2.5 should have been named v0.3.0 due to compatibility-breaking changes.
Since the release v0.2.5 is already yanked (of sorts), a smooth upgrade seems inexpedient.

Before upgrading to the v0.3.0, please pay attention to the incompatible changes:

1. the `connect` function from the `async/connection.py` module is now asynchronous context manager.
2. the same kind of changes touches the `create_pool` function from the `async/pool.py` module.
3. all deprecated methods from `Connection`, `Cursor` and `Pool` classes are removed.

For more details, please refer to the project [CHANGELOG.md](./CHANGELOG.md) file.

## Usage

Basically, a connection to a ClickHouse server can be established in two ways:
Expand All @@ -32,9 +45,24 @@ Basically, a connection to a ClickHouse server can be established in two ways:

# connecting with a DSN string
async def connect_database():
conn = await connect(
async with connect(
dsn = "clickhouse://ch_user:P@55w0rD:@127.0.0.1:9000/chdb",
) as conn:
pass
```

or

```python
from asynch import Connection

# connecting with a DSN string
async def connect_database():
conn = Connection(
dsn = "clickhouse://ch_user:P@55w0rD:@127.0.0.1:9000/chdb",
)
async with conn:
pass
```

2. with separately given connection/DSN parameters: `user` (optional), `password` (optional), `host`, `port`, `database`.
Expand All @@ -44,13 +72,31 @@ Basically, a connection to a ClickHouse server can be established in two ways:

# connecting with DSN parameters
async def connect_database():
conn = await connect(
async with connect(
user = "ch_user",
password = "P@55w0rD",
host = "127.0.0.1",
port = 9000,
database = "chdb",
)
) as conn:
pass
```

or

```python
from asynch import Connection

# connecting with DSN parameters
async def connect_database():
async with Connection(
user = "ch_user",
password = "P@55w0rD",
host = "127.0.0.1",
port = 9000,
database = "chdb",
) as conn:
pass
```

If a DSN string is given, it takes priority over any specified connection parameter.
Expand Down Expand Up @@ -181,7 +227,7 @@ Since the v0.2.5:

```python
async def use_pool():
# init a Pool and fill it with `minsize` opened connections
# init a Pool and fill it with the `minsize` opened connections
async with Pool(minsize=1, maxsize=2) as pool:
# acquire a connection from the pool
async with pool.connection() as conn:
Expand All @@ -191,6 +237,28 @@ async def use_pool():
assert ret == (1,)
```

which decomposes into

```python
async def use_pool():
pool = Pool(minsize=1, maxsize=2)
await pool.startup()

# some logic

await pool.shutdown()
```

Since the v0.3.0 the `create_pool` is asynchronous context manager:

```python
from asynch.pool import create_pool

async def use_pool():
async with create_pool(minsize=1, maxsize=2) as pool:
# some logic
```

## ThanksTo

- [clickhouse-driver](https://github.com/mymarilyn/clickhouse-driver), ClickHouse Python Driver with native interface support.
Expand Down
97 changes: 54 additions & 43 deletions asynch/connection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import Optional, Type
from warnings import warn

from asynch.cursors import Cursor
from asynch.errors import NotSupportedError
Expand Down Expand Up @@ -67,30 +68,6 @@ def __repr__(self) -> str:
status = self.status
return f"<{cls_name} object at 0x{id(self):x}; status: {status}>"

@property
def connected(self) -> Optional[bool]:
"""Returns the connection open status.

If the return value is None,
the connection was only created,
but neither opened or closed.

The attribute is deprecated in favour of `opened` one.
The reason is about tautology on `connection.connected` case.

:returns: the connection open status
:rtype: None | bool
"""

warn(
(
"Please consider using the `opened` property. "
"The `connected` property may be removed in the version 0.2.6 or later."
),
DeprecationWarning,
)
return self._opened

@property
def opened(self) -> Optional[bool]:
"""Returns the connection open status.
Expand Down Expand Up @@ -137,11 +114,12 @@ def status(self) -> str:
:rtype: str (ConnectionStatus StrEnum)
"""

if self._opened is None and self._closed is None:
opened, closed = self._opened, self._closed
if opened is None and closed is None:
return ConnectionStatus.created
if self._opened:
if opened:
return ConnectionStatus.opened
if self._closed:
if closed:
return ConnectionStatus.closed
raise ConnectionError(f"{self} is in an unknown state")

Expand Down Expand Up @@ -172,17 +150,16 @@ def echo(self) -> bool:
async def close(self) -> None:
"""Close the connection."""

if self._closed:
return
if self._opened:
await self._connection.disconnect()
self._opened = False
self._closed = True
self._opened = False
self._closed = True

async def commit(self):
raise NotSupportedError

async def rollback(self):
raise NotSupportedError
stankudrow marked this conversation as resolved.
Show resolved Hide resolved

async def connect(self) -> None:
if not self._opened:
await self._connection.connect()
Expand All @@ -199,8 +176,8 @@ def cursor(self, cursor: Optional[Type[Cursor]] = None, *, echo: bool = False) -
of a default `Cursor` class will be created with echoing
set to True even if the `self.echo` property returns False.

:param cursor Optional[Cursor]: Cursor factory class
:param echo bool: to override the `Connection.echo` parametre for a cursor
:param cursor Optional[Type[Cursor]]: Cursor factory class
:param echo bool: to override the `Connection.echo` parameter for a cursor

:return: the cursor object of a connection
:rtype: Cursor
Expand All @@ -220,7 +197,38 @@ async def ping(self) -> None:
msg = f"Ping has failed for {self}"
raise ConnectionError(msg)

async def _refresh(self) -> None:
"""Refresh the connection.

It does ping and if it fails,
attempts to connect again.
If reconnecting fails,
an exception is raised and
the connection cannot be refreshed

:raises ConnectionError: refreshing already closed connection

:return: None
"""

status = self.status
if status == ConnectionStatus.created:
msg = f"the {self} is not opened to be refreshed"
raise ConnectionError(msg)
if status == ConnectionStatus.closed:
msg = f"the {self} is already closed"
raise ConnectionError(msg)

try:
await self.ping()
except ConnectionError:
await self.connect()

async def rollback(self):
raise NotSupportedError


@asynccontextmanager
async def connect(
dsn: Optional[str] = None,
user: str = constants.DEFAULT_USER,
Expand All @@ -231,16 +239,16 @@ async def connect(
cursor_cls=Cursor,
echo: bool = False,
**kwargs,
) -> Connection:
) -> AsyncIterator[Connection]:
"""Return an opened connection to a ClickHouse server.

Equivalent to the following steps:
Before the v0.3.0, was equivalent to:
1. conn = Connection(...) # init a Connection instance
2. conn.connect() # connect to a ClickHouse server
3. return conn

When the connection is no longer needed,
consider `await`ing the `conn.close()` method.
Since the v0.3.0 is an asynchronous context manager
that handles resource clean-up.

:param dsn str: DSN/connection string (if None -> constructed from default dsn parts)
:param user str: user string ("default" by default)
Expand All @@ -252,8 +260,8 @@ async def connect(
:param echo bool: connection echo mode (False by default)
:param kwargs dict: connection settings

:return: an opened Connection object
:rtype: Connection
:return: an async Connection context manager
:rtype: AsyncIterator[Connection]
"""

conn = Connection(
Expand All @@ -267,5 +275,8 @@ async def connect(
echo=echo,
**kwargs,
)
await conn.connect()
return conn
try:
await conn.connect()
yield conn
finally:
await conn.close()
Loading
Loading