Skip to content

Commit c72d79e

Browse files
authored
Merge pull request piccolo-orm#65 from piccolo-orm/improved_engine_setup
modified postgres engine - can specify extensions to install on start
2 parents 06e90ae + 5622304 commit c72d79e

File tree

6 files changed

+174
-134
lines changed

6 files changed

+174
-134
lines changed

docs/src/piccolo/engines/index.rst

Lines changed: 9 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ It's important that each ``Table`` class knows which engine to use. There are
1010
two ways of doing this - setting it explicitly via the ``db`` argument, or
1111
letting Piccolo find it using ``engine_finder``.
1212

13+
-------------------------------------------------------------------------------
14+
1315
Explicit
1416
--------
1517

@@ -30,6 +32,7 @@ connect to a database.
3032
class MyTable(Table, db=DB):
3133
name = Varchar()
3234
35+
-------------------------------------------------------------------------------
3336

3437
engine_finder
3538
-------------
@@ -94,6 +97,8 @@ variable accordingly.
9497
9598
.. hint:: You can also specify sub modules, like `my_module.piccolo_conf`.
9699

100+
-------------------------------------------------------------------------------
101+
97102
.. _EngineTypes:
98103

99104
Engine types
@@ -103,83 +108,8 @@ Engine types
103108
production. It is the most feature complete.
104109

105110

106-
SQLiteEngine
107-
~~~~~~~~~~~~
108-
109-
.. code-block:: python
110-
111-
# piccolo_conf.py
112-
from piccolo.engine.sqlite import SQLiteEngine
113-
114-
115-
DB = SQLiteEngine(path='my_app.sqlite')
116-
117-
118-
PostgresEngine
119-
~~~~~~~~~~~~~~
120-
121-
.. code-block:: python
122-
123-
# piccolo_conf.py
124-
from piccolo.engine.postgres import PostgresEngine
125-
126-
127-
DB = PostgresEngine({
128-
'host': 'localhost',
129-
'database': 'my_app',
130-
'user': 'postgres',
131-
'password': ''
132-
})
133-
134-
Connection pool
135-
---------------
136-
137-
.. warning:: This is currently only available for Postgres.
138-
139-
140-
To use a connection pool, you need to first initialise it. The best place to do
141-
this is in the startup event handler of whichever web framework you are using.
142-
143-
Here's an example using Starlette. Notice that we also close the connection
144-
pool in the shutdown event handler.
145-
146-
.. code-block:: python
147-
148-
from piccolo.engine import from starlette.applications import Starlette
149-
from starlette.applications import Starlette
150-
151-
152-
app = Starlette()
153-
154-
155-
@app.on_event('startup')
156-
async def open_database_connection_pool():
157-
engine = engine_finder()
158-
await engine.start_connnection_pool()
159-
160-
161-
@app.on_event('shutdown')
162-
async def close_database_connection_pool():
163-
engine = engine_finder()
164-
await engine.close_connnection_pool()
165-
166-
.. hint:: Using a connection pool helps with performance, since connections
167-
are reused instead of being created for each query.
168-
169-
Once a connection pool has been started, the engine will use it for making
170-
queries.
171-
172-
.. hint:: If you're running several instances of an app on the same server,
173-
you may prefer an external connection pooler - like pgbouncer.
174-
175-
Configuration
176-
~~~~~~~~~~~~~
177-
178-
The connection pool uses the same configuration as your engine. You can also
179-
pass in additional parameters, which are passed to the underlying database
180-
adapter. Here's an example:
181-
182-
.. code-block:: python
111+
.. toctree::
112+
:maxdepth: 1
183113

184-
# To increase the number of connections available:
185-
await engine.start_connnection_pool(max_size=20)
114+
./sqlite_engine
115+
./postgres_engine
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
PostgresEngine
2+
==============
3+
4+
Configuration
5+
-------------
6+
7+
.. code-block:: python
8+
9+
# piccolo_conf.py
10+
from piccolo.engine.postgres import PostgresEngine
11+
12+
13+
DB = PostgresEngine(config={
14+
'host': 'localhost',
15+
'database': 'my_app',
16+
'user': 'postgres',
17+
'password': ''
18+
})
19+
20+
config
21+
~~~~~~
22+
23+
The config dictionary is passed directly to the underlying database adapter,
24+
asyncpg. See the `asyncpg docs <https://magicstack.github.io/asyncpg/current/api/index.html#connection>`_
25+
to learn more.
26+
27+
-------------------------------------------------------------------------------
28+
29+
Connection pool
30+
---------------
31+
32+
To use a connection pool, you need to first initialise it. The best place to do
33+
this is in the startup event handler of whichever web framework you are using.
34+
35+
Here's an example using Starlette. Notice that we also close the connection
36+
pool in the shutdown event handler.
37+
38+
.. code-block:: python
39+
40+
from piccolo.engine import from starlette.applications import Starlette
41+
from starlette.applications import Starlette
42+
43+
44+
app = Starlette()
45+
46+
47+
@app.on_event('startup')
48+
async def open_database_connection_pool():
49+
engine = engine_finder()
50+
await engine.start_connnection_pool()
51+
52+
53+
@app.on_event('shutdown')
54+
async def close_database_connection_pool():
55+
engine = engine_finder()
56+
await engine.close_connnection_pool()
57+
58+
.. hint:: Using a connection pool helps with performance, since connections
59+
are reused instead of being created for each query.
60+
61+
Once a connection pool has been started, the engine will use it for making
62+
queries.
63+
64+
.. hint:: If you're running several instances of an app on the same server,
65+
you may prefer an external connection pooler - like pgbouncer.
66+
67+
Configuration
68+
~~~~~~~~~~~~~
69+
70+
The connection pool uses the same configuration as your engine. You can also
71+
pass in additional parameters, which are passed to the underlying database
72+
adapter. Here's an example:
73+
74+
.. code-block:: python
75+
76+
# To increase the number of connections available:
77+
await engine.start_connnection_pool(max_size=20)
78+
79+
-------------------------------------------------------------------------------
80+
81+
Source
82+
------
83+
84+
.. currentmodule:: piccolo.engine.postgres
85+
86+
.. autoclass:: PostgresEngine
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
SQLiteEngine
2+
============
3+
4+
Configuration
5+
-------------
6+
7+
The ``SQLiteEngine`` is very simple - just specify a file path. The database
8+
file will be created automatically if it doesn't exist.
9+
10+
.. code-block:: python
11+
12+
# piccolo_conf.py
13+
from piccolo.engine.sqlite import SQLiteEngine
14+
15+
16+
DB = SQLiteEngine(path='my_app.sqlite')
17+
18+
-------------------------------------------------------------------------------
19+
20+
Source
21+
------
22+
23+
.. currentmodule:: piccolo.engine.sqlite
24+
25+
.. autoclass:: SQLiteEngine

piccolo/engine/base.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from abc import abstractmethod, ABCMeta
33
import typing as t
44

5+
from piccolo.utils.sync import run_sync
56
from piccolo.utils.warnings import colored_warning, Level
67

78
if t.TYPE_CHECKING: # pragma: no cover
@@ -14,8 +15,8 @@ class Batch:
1415

1516
class Engine(metaclass=ABCMeta):
1617
def __init__(self):
17-
self.check_version()
18-
self.prep_database()
18+
run_sync(self.check_version())
19+
run_sync(self.prep_database())
1920

2021
@property
2122
@abstractmethod
@@ -28,23 +29,23 @@ def min_version_number(self) -> float:
2829
pass
2930

3031
@abstractmethod
31-
def get_version(self) -> float:
32+
async def get_version(self) -> float:
3233
pass
3334

3435
@abstractmethod
35-
def prep_database(self):
36+
async def prep_database(self):
3637
pass
3738

3839
@abstractmethod
3940
async def batch(self, query: Query, batch_size: int = 100) -> Batch:
4041
pass
4142

42-
def check_version(self):
43+
async def check_version(self):
4344
"""
4445
Warn if the database version isn't supported.
4546
"""
4647
try:
47-
version_number = self.get_version()
48+
version_number = await self.get_version()
4849
except Exception as exception:
4950
colored_warning(
5051
f"Unable to fetch server version: {exception}",

piccolo/engine/postgres.py

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
from __future__ import annotations
2-
import asyncio
3-
from concurrent.futures import ThreadPoolExecutor
42
import contextvars
53
from dataclasses import dataclass
64
import typing as t
@@ -195,14 +193,10 @@ async def __aexit__(self, exception_type, exception, traceback):
195193

196194

197195
class PostgresEngine(Engine):
196+
"""
197+
Used to connect to Postgresql.
198198
199-
__slots__ = ("config", "pool")
200-
201-
engine_type = "postgres"
202-
min_version_number = 9.6
203-
204-
def __init__(self, config: t.Dict[str, t.Any]) -> None:
205-
"""
199+
:param config:
206200
The config dictionary is passed to the underlying database adapter,
207201
asyncpg. Common arguments you're likely to need are:
208202
@@ -218,8 +212,24 @@ def __init__(self, config: t.Dict[str, t.Any]) -> None:
218212
219213
* https://magicstack.github.io/asyncpg/current/api/index.html#connection
220214
221-
""" # noqa: E501
215+
:param extensions:
216+
When the engine starts, it will try and create these extensions
217+
in Postgres.
218+
219+
""" # noqa: E501
220+
221+
__slots__ = ("config", "extensions", "pool", "transaction_connection")
222+
223+
engine_type = "postgres"
224+
min_version_number = 9.6
225+
226+
def __init__(
227+
self,
228+
config: t.Dict[str, t.Any],
229+
extensions: t.Sequence[str] = ["uuid-ossp"],
230+
) -> None:
222231
self.config = config
232+
self.extensions = extensions
223233
self.pool: t.Optional[Pool] = None
224234
database_name = config.get("database", "Unknown")
225235
self.transaction_connection = contextvars.ContextVar(
@@ -241,20 +251,14 @@ def _parse_raw_version_string(version_string: str) -> float:
241251
version = float(f"{major}.{minor}")
242252
return version
243253

244-
def get_version(self) -> float:
254+
async def get_version(self) -> float:
245255
"""
246256
Returns the version of Postgres being run.
247257
"""
248-
loop = asyncio.new_event_loop()
249-
250-
with ThreadPoolExecutor(max_workers=1) as executor:
251-
future = executor.submit(
252-
loop.run_until_complete,
253-
self._run_in_new_connection("SHOW server_version"),
254-
)
255-
256258
try:
257-
response: t.Sequence[t.Dict] = future.result() # type: ignore
259+
response: t.Sequence[t.Dict] = await self._run_in_new_connection(
260+
"SHOW server_version"
261+
)
258262
except ConnectionRefusedError as exception:
259263
# Suppressing the exception, otherwise importing piccolo_conf.py
260264
# containing an engine will raise an ImportError.
@@ -267,26 +271,20 @@ def get_version(self) -> float:
267271
version_string=version_string
268272
)
269273

270-
def prep_database(self):
271-
loop = asyncio.new_event_loop()
272-
273-
with ThreadPoolExecutor(max_workers=1) as executor:
274-
future = executor.submit(
275-
loop.run_until_complete,
276-
self._run_in_new_connection(
277-
'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
278-
),
279-
)
280-
281-
try:
282-
future.result()
283-
except InsufficientPrivilegeError:
284-
print(
285-
"Unable to create uuid-ossp extension - UUID columns might "
286-
"not behave as expected. Make sure your database user has "
287-
"permission to create extensions, or add it manually using "
288-
'`CREATE EXTENSION "uuid-ossp";`'
289-
)
274+
async def prep_database(self):
275+
for extension in self.extensions:
276+
try:
277+
await self._run_in_new_connection(
278+
f'CREATE EXTENSION IF NOT EXISTS "{extension}"',
279+
)
280+
except InsufficientPrivilegeError:
281+
print(
282+
f"Unable to create {extension} extension - some "
283+
"functionality may not behave as expected. Make sure your "
284+
"database user has permission to create extensions, or "
285+
"add it manually using "
286+
f'`CREATE EXTENSION "{extension}";`'
287+
)
290288

291289
###########################################################################
292290

0 commit comments

Comments
 (0)