Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Commit fbe39bf

Browse files
Merge pull request #46 from encode/add-connection-options
Add connection options
2 parents 9bee7cb + 784470f commit fbe39bf

File tree

8 files changed

+99
-10
lines changed

8 files changed

+99
-10
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,19 @@ async def shutdown():
173173
await database.disconnect()
174174
```
175175

176+
## Connection options
177+
178+
The PostgreSQL and MySQL backends provide a few connection options for SSL
179+
and for configuring the connection pool.
180+
181+
```python
182+
# Use an SSL connection.
183+
database = Database('postgresql://localhost/example?ssl=true')
184+
185+
# Use a connection pool of between 5-20 connections.
186+
database = Database('mysql://localhost/example?min_size=5&max_size=20')
187+
```
188+
176189
## Test isolation
177190

178191
For strict test isolation you will always want to rollback the test database

databases/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from databases.core import Database, DatabaseURL
22

33

4-
__version__ = "0.1.6"
4+
__version__ = "0.1.7"
55
__all__ = ["Database", "DatabaseURL"]

databases/backends/mysql.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,38 @@
1717

1818

1919
class MySQLBackend(DatabaseBackend):
20-
def __init__(self, database_url: DatabaseURL) -> None:
21-
self._database_url = database_url
20+
def __init__(self, database_url: typing.Union[DatabaseURL, str]) -> None:
21+
self._database_url = DatabaseURL(database_url)
2222
self._dialect = pymysql.dialect(paramstyle="pyformat")
2323
self._pool = None
2424

25+
def _get_connection_kwargs(self) -> dict:
26+
options = self._database_url.options
27+
28+
kwargs = {}
29+
min_size = options.get("min_size")
30+
max_size = options.get("max_size")
31+
ssl = options.get("ssl")
32+
33+
if min_size is not None:
34+
kwargs["minsize"] = int(min_size)
35+
if max_size is not None:
36+
kwargs["maxsize"] = int(max_size)
37+
if ssl is not None:
38+
kwargs["ssl"] = {"true": True, "false": False}[ssl.lower()]
39+
return kwargs
40+
2541
async def connect(self) -> None:
2642
assert self._pool is None, "DatabaseBackend is already running"
43+
kwargs = self._get_connection_kwargs()
2744
self._pool = await aiomysql.create_pool(
2845
host=self._database_url.hostname,
2946
port=self._database_url.port or 3306,
3047
user=self._database_url.username or getpass.getuser(),
3148
password=self._database_url.password,
3249
db=self._database_url.database,
3350
autocommit=True,
51+
**kwargs,
3452
)
3553

3654
async def disconnect(self) -> None:

databases/backends/postgres.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717

1818
class PostgresBackend(DatabaseBackend):
19-
def __init__(self, database_url: DatabaseURL) -> None:
20-
self._database_url = database_url
19+
def __init__(self, database_url: typing.Union[DatabaseURL, str]) -> None:
20+
self._database_url = DatabaseURL(database_url)
2121
self._dialect = self._get_dialect()
2222
self._pool = None
2323

@@ -33,9 +33,26 @@ def _get_dialect(self) -> Dialect:
3333

3434
return dialect
3535

36+
def _get_connection_kwargs(self) -> dict:
37+
options = self._database_url.options
38+
39+
kwargs = {}
40+
min_size = options.get("min_size")
41+
max_size = options.get("max_size")
42+
ssl = options.get("ssl")
43+
44+
if min_size is not None:
45+
kwargs["min_size"] = int(min_size)
46+
if max_size is not None:
47+
kwargs["max_size"] = int(max_size)
48+
if ssl is not None:
49+
kwargs["ssl"] = {"true": True, "false": False}[ssl.lower()]
50+
return kwargs
51+
3652
async def connect(self) -> None:
3753
assert self._pool is None, "DatabaseBackend is already running"
38-
self._pool = await asyncpg.create_pool(str(self._database_url))
54+
kwargs = self._get_connection_kwargs()
55+
self._pool = await asyncpg.create_pool(str(self._database_url), **kwargs)
3956

4057
async def disconnect(self) -> None:
4158
assert self._pool is not None, "DatabaseBackend is not running"

databases/backends/sqlite.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616

1717

1818
class SQLiteBackend(DatabaseBackend):
19-
def __init__(self, database_url: DatabaseURL) -> None:
20-
self._database_url = database_url
19+
def __init__(self, database_url: typing.Union[DatabaseURL, str]) -> None:
20+
self._database_url = DatabaseURL(database_url)
2121
self._dialect = pysqlite.dialect(paramstyle="qmark")
22-
self._pool = SQLitePool(database_url)
22+
self._pool = SQLitePool(self._database_url)
2323

2424
async def connect(self) -> None:
2525
pass

databases/core.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
import typing
55
from types import TracebackType
6-
from urllib.parse import SplitResult, urlsplit
6+
from urllib.parse import SplitResult, parse_qsl, urlsplit
77

88
from sqlalchemy.engine import RowProxy
99
from sqlalchemy.sql import ClauseElement
@@ -295,6 +295,12 @@ def netloc(self) -> typing.Optional[str]:
295295
def database(self) -> str:
296296
return self.components.path.lstrip("/")
297297

298+
@property
299+
def options(self) -> dict:
300+
if not hasattr(self, "_options"):
301+
self._options = dict(parse_qsl(self.components.query))
302+
return self._options
303+
298304
def replace(self, **kwargs: typing.Any) -> "DatabaseURL":
299305
if (
300306
"username" in kwargs

tests/test_connection_options.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""
2+
Unit tests for the backend connection arguments.
3+
"""
4+
5+
from databases.backends.mysql import MySQLBackend
6+
from databases.backends.postgres import PostgresBackend
7+
8+
9+
def test_postgres_pool_size():
10+
backend = PostgresBackend("postgres://localhost/database?min_size=1&max_size=20")
11+
kwargs = backend._get_connection_kwargs()
12+
assert kwargs == {"min_size": 1, "max_size": 20}
13+
14+
15+
def test_postgres_ssl():
16+
backend = PostgresBackend("postgres://localhost/database?ssl=true")
17+
kwargs = backend._get_connection_kwargs()
18+
assert kwargs == {"ssl": True}
19+
20+
21+
def test_mysql_pool_size():
22+
backend = MySQLBackend("mysql://localhost/database?min_size=1&max_size=20")
23+
kwargs = backend._get_connection_kwargs()
24+
assert kwargs == {"minsize": 1, "maxsize": 20}
25+
26+
27+
def test_mysql_ssl():
28+
backend = MySQLBackend("postgres://localhost/database?ssl=true")
29+
kwargs = backend._get_connection_kwargs()
30+
assert kwargs == {"ssl": True}

tests/test_database_url.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ def test_database_url_properties():
2323
assert u.database == "mydatabase"
2424

2525

26+
def test_database_url_options():
27+
u = DatabaseURL("postgresql://localhost/mydatabase?pool_size=20&ssl=true")
28+
assert u.options == {"pool_size": "20", "ssl": "true"}
29+
30+
2631
def test_replace_database_url_components():
2732
u = DatabaseURL("postgresql://localhost/mydatabase")
2833

0 commit comments

Comments
 (0)