Skip to content

Commit 7753503

Browse files
committed
add more FAQ
1 parent b76b4f0 commit 7753503

File tree

3 files changed

+290
-1
lines changed

3 files changed

+290
-1
lines changed

docs/how-to/faq.rst

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ core grammars. Besides GINO can be used in a completely `non-ORM way
1818
<schema.html#gino-core>`__.
1919

2020

21+
Can I use features of SQLAlchemy ORM?
22+
-------------------------------------
23+
24+
SQLAlchemy has `two parts <https://docs.sqlalchemy.org/en/13/>`__:
25+
26+
* SQLAlchemy Core
27+
* SQLAlchemy ORM
28+
29+
GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO.
30+
31+
2132
How to join?
2233
------------
2334

@@ -58,3 +69,276 @@ If you are using Python 3.7, then ``aiocontextvars`` does nothing at all.
5869

5970
This answer is for GINO 0.8 and later, please check earlier versions of
6071
this documentation if you are using GINO 0.7.
72+
73+
74+
How to define relationships?
75+
----------------------------
76+
77+
GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs.
78+
However, you could always manually define your tables, design your queries and load the
79+
results explicitly in GINO. Please see :doc:`loaders` for more information.
80+
81+
82+
How to define index with multiple columns?
83+
------------------------------------------
84+
85+
::
86+
87+
class User(db.Model):
88+
__tablename__ = 'users'
89+
90+
first_name = db.Column(db.Unicode())
91+
last_name = db.Column(db.Unicode())
92+
93+
_name_idx = db.Index('index_on_name', 'first_name', 'last_name')
94+
95+
The ``_name_idx`` is not used.
96+
97+
98+
Is there a django admin interface for GINO?
99+
-------------------------------------------
100+
101+
Not quite yet, please follow `this discussion
102+
<https://github.com/python-gino/gino/issues/260>`__.
103+
104+
105+
How to use multiple databases for different users on the fly?
106+
-------------------------------------------------------------
107+
108+
GINO models are linked to a :class:`~gino.api.Gino` instance, while
109+
:class:`~gino.api.Gino` has an optional property ``bind`` to hold a
110+
:class:`~gino.engine.GinoEngine` instance. So when you are executing::
111+
112+
user = await User.get(request.user_id)
113+
114+
The ``bind`` is implicitly used to execute the query.
115+
116+
In order to use multiple databases, you would need multiple
117+
:class:`~gino.engine.GinoEngine` instances. Here's a full example using FastAPI with
118+
lazy engine creation::
119+
120+
from asyncio import Future
121+
from contextvars import ContextVar
122+
123+
from fastapi import FastAPI, Request
124+
from gino import create_engine
125+
from gino.ext.starlette import Gino
126+
127+
engines = {}
128+
dbname = ContextVar("dbname")
129+
130+
131+
class ContextualGino(Gino):
132+
@property
133+
def bind(self):
134+
e = engines.get(dbname.get(""))
135+
if e and e.done():
136+
return e.result()
137+
else:
138+
return self._bind
139+
140+
@bind.setter
141+
def bind(self, val):
142+
self._bind = val
143+
144+
145+
app = FastAPI()
146+
db = ContextualGino(app)
147+
148+
149+
@app.middleware("http")
150+
async def lazy_engines(request: Request, call_next):
151+
name = request.query_params.get("db", "postgres")
152+
fut = engines.get(name)
153+
if fut is None:
154+
fut = engines[name] = Future()
155+
try:
156+
engine = await create_engine("postgresql://localhost/" + name)
157+
except Exception as e:
158+
fut.set_exception(e)
159+
del engines[name]
160+
raise
161+
else:
162+
fut.set_result(engine)
163+
await fut
164+
dbname.set(name)
165+
return await call_next(request)
166+
167+
168+
@app.get("/")
169+
async def get():
170+
return dict(dbname=await db.scalar("SELECT current_database()"))
171+
172+
173+
How to load complex query results?
174+
----------------------------------
175+
176+
The API doc of :mod:`gino.loader` explains the available loaders, and there're a few
177+
examples in :doc:`loaders` too.
178+
179+
Below is an example with a joined result to load both a GINO model and an integer at the
180+
same time, using a :class:`~gino.loader.TupleLoader` with two sub-loaders -
181+
:class:`~gino.loader.ModelLoader` and :class:`~gino.loader.ColumnLoader`::
182+
183+
import asyncio
184+
import random
185+
import string
186+
187+
import gino
188+
from gino.loader import ColumnLoader
189+
190+
db = gino.Gino()
191+
192+
193+
class User(db.Model):
194+
__tablename__ = 'users'
195+
196+
id = db.Column(db.Integer(), primary_key=True)
197+
name = db.Column(db.Unicode())
198+
199+
200+
class Visit(db.Model):
201+
__tablename__ = 'visits'
202+
203+
id = db.Column(db.Integer(), primary_key=True)
204+
time = db.Column(db.DateTime(), server_default='now()')
205+
user_id = db.Column(db.ForeignKey('users.id'))
206+
207+
208+
async def main():
209+
async with db.with_bind('postgresql://localhost/gino'):
210+
await db.gino.create_all()
211+
212+
for i in range(random.randint(5, 10)):
213+
u = await User.create(
214+
name=''.join(random.choices(string.ascii_letters, k=10)))
215+
for v in range(random.randint(10, 20)):
216+
await Visit.create(user_id=u.id)
217+
218+
visits = db.func.count(Visit.id)
219+
q = db.select([
220+
User,
221+
visits,
222+
]).select_from(
223+
User.outerjoin(Visit)
224+
).group_by(
225+
*User,
226+
).gino.load((User, ColumnLoader(visits)))
227+
async with db.transaction():
228+
async for user, visits in q.iterate():
229+
print(user.name, visits)
230+
231+
await db.gino.drop_all()
232+
233+
234+
asyncio.run(main())
235+
236+
Be ware of the :class:`tuple` in ``.gino.load((...))``.
237+
238+
239+
How to do bulk insert / update?
240+
-------------------------------
241+
242+
TBD #314
243+
244+
245+
How to print the executed SQL?
246+
------------------------------
247+
248+
GINO uses the same approach from SQLAlchemy: ``create_engine(..., echo=True)``.
249+
(Or ``db.set_bind(..., echo=True)``) Please see also `here
250+
<https://docs.sqlalchemy.org/en/13/core/engines.html#sqlalchemy.create_engine.params.echo>`__.
251+
252+
If you use any extension, you can also set that in config, by `db_echo` or `DB_ECHO`.
253+
254+
255+
How to run ``EXISTS`` SQL?
256+
--------------------------
257+
258+
::
259+
260+
await db.scalar(db.exists().where(User.email == email).select())
261+
262+
263+
How to work with Alembic?
264+
-------------------------
265+
266+
The fact that :class:`~gino.api.Gino` is a :class:`~sqlalchemy.schema.MetaData` is the
267+
key to use Alembic. Just import and set ``target_metadata = db`` in Alembic ``env.py``
268+
will do. See :doc:`alembic` for more details.
269+
270+
271+
How to join the same table twice?
272+
---------------------------------
273+
274+
This is the same pattern as described in SQLAlchemy :ref:`self_referential`, where you
275+
have a table with "a foreign key reference to itself", or join the same table more than
276+
once, "to represent hierarchical data in flat tables." We'd need to use
277+
:func:`~gino.crud.CRUDModel.alias`, for example::
278+
279+
class User(db.Model):
280+
__tablename__ = "users"
281+
282+
id = db.Column(db.Integer, primary_key=True)
283+
parent_id = db.Column(db.ForeignKey("users.id"))
284+
285+
Parent = User.alias()
286+
query = User.outerjoin(Parent, User.parent_id == Parent.id).select()
287+
users = await query.gino.load(User.load(parent=Parent)).all()
288+
289+
290+
.. _raw-sql:
291+
292+
How to execute raw SQL with parameters?
293+
---------------------------------------
294+
295+
Wrap the SQL with :func:`~sqlalchemy.sql.expression.text`, and use keyword arguments::
296+
297+
query = db.text('SELECT * FROM users WHERE id = :id_val')
298+
row = await db.first(query, id_val=1)
299+
300+
You may even load the rows into model instances::
301+
302+
query = query.execution_options(loader=User)
303+
user = await db.first(query, id_val=1)
304+
305+
306+
Gino engine is not initialized?
307+
-------------------------------
308+
309+
GINO models are linked to a :class:`~gino.api.Gino` instance, while
310+
:class:`~gino.api.Gino` has an optional property ``bind`` to hold a
311+
:class:`~gino.engine.GinoEngine` instance. So when you are executing::
312+
313+
user = await User.get(request.user_id)
314+
315+
The ``bind`` is implicitly used to execute the query. If ``bind`` is not set before
316+
this, you'll see this error:
317+
318+
.. code-block:: text
319+
320+
gino.exceptions.UninitializedError: Gino engine is not initialized.
321+
322+
You could use either:
323+
324+
* Call :meth:`~gino.api.Gino.set_bind` or :meth:`~gino.api.Gino.with_bind` to set the
325+
bind on the :class:`~gino.api.Gino` instance.
326+
* Use one of the Web framework extensions to set the bind for you in usually the server
327+
start-up hook.
328+
* Use explicit ``bind`` for each execution, for example::
329+
330+
engine = await create_engine("...")
331+
# ...
332+
user = await User.get(request.user_id, bind=engine)
333+
334+
335+
How can I do SQL xxxx in GINO?
336+
------------------------------
337+
338+
GINO uses `SQLAlchemy Core <https://docs.sqlalchemy.org/en/13/core/>`__ queries, so
339+
please check its documentation on how to build queries. The GINO models are simply
340+
wrappers of SQLAlchemy :class:`~sqlalchemy.schema.Table` instances, and the column
341+
attributes on GINO model classes are just SQLAlchemy :class:`~sqlalchemy.schema.Column`
342+
instances, you can use them in building your SQLAlchemy Core queries.
343+
344+
Alternatively, you could always execute the raw SQL directly, see :ref:`raw-sql` above.

docs/theme/static/css/gino.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ div.search i {
295295
overflow-y: auto;
296296
}
297297

298+
.table-of-contents > ul {
299+
padding-bottom: 80px;
300+
}
301+
298302
.table-of-contents li {
299303
padding: 0;
300304
}

docs/theme/static/js/gino.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,8 @@ document.addEventListener('DOMContentLoaded', function () {
575575
sr.style.display = 'block';
576576
}
577577
var scrollOffset = 64 + (window.innerHeight - 64) / 5;
578-
var anchors = document.querySelectorAll('dt, a.footnote-reference');
578+
var anchors = document.querySelectorAll(
579+
'dt[id]:not([id=""]), a.footnote-reference, span[id]:not([id=""])');
579580
window.addEventListener('click', function (e) {
580581
if (!sc.contains(e.target)) {
581582
sr.style.display = 'none';

0 commit comments

Comments
 (0)