Skip to content

Commit 902f28d

Browse files
committed
Fix: Lazy behaviour. Connect to database on app.ready
1 parent ff1be14 commit 902f28d

File tree

5 files changed

+72
-56
lines changed

5 files changed

+72
-56
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ omit =
44
dist
55
per-file-ignores =
66
yhttp/ext/sqlalchemy/__init__.py: F401
7-
tests/conftest.py: F401
7+
tests/conftest.py: F401, F811

tests/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88

99

1010
@pytest.fixture
11-
def app():
11+
def app(freshdb):
1212
app = Application()
13+
app.settings.merge(f'''
14+
db:
15+
url: {freshdb}
16+
''')
1317
yield app
1418
app.shutdown()
1519

tests/test_extension.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,7 @@
88
from yhttp.ext import sqlalchemy as saext, dbmanager
99

1010

11-
def test_extension(app, Given, freshdb):
12-
app.settings.merge(f'''
13-
db:
14-
url: {freshdb}
15-
''')
16-
11+
def test_extension(Given, freshdb, app):
1712
class Base(DeclarativeBase):
1813
pass
1914

@@ -28,19 +23,24 @@ class Foo(Base):
2823
app.ready()
2924
app.db.create_objects()
3025

31-
def mockup():
32-
with app.db.session() as session:
33-
foo = Foo(title='foo 1')
34-
bar = Foo(title='foo 2')
35-
session.add_all([foo, bar])
26+
with app.db.session() as session:
27+
foo = Foo(title='foo 1')
28+
session.add(foo)
29+
session.commit()
30+
session.reset()
3631

37-
mockup()
32+
with app.db.copy(freshdb) as d, d.session() as session:
33+
bar = Foo(title='foo 2')
34+
session.add(bar)
35+
session.commit()
36+
session.reset()
3837

3938
@app.route()
4039
@json
4140
@app.db
4241
def get(req):
43-
result = req.dbsession.scalars(select(Foo)).all()
42+
with app.db.session() as session:
43+
result = session.scalars(select(Foo)).all()
4444
return {f.id: f.title for f in result}
4545

4646
@app.route()
@@ -79,8 +79,6 @@ def getfoo(title):
7979
foo = getfoo('foo 1')
8080
assert foo is not None
8181

82-
app.shutdown()
83-
8482

8583
def test_exceptions(app, freshdb):
8684
class Base(DeclarativeBase):

yhttp/ext/sqlalchemy/install.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ def install(app, basemodel, db=None, cliarguments=None):
99
cli.DatabaseObjectsCommand.__arguments__.extend(cliarguments)
1010

1111
if db is None:
12-
db = orm.DatabaseManager(app, basemodel)
12+
db = orm.ApplicationORM(basemodel, app)
1313

1414
if db.engine is None:
1515
@app.when
1616
def ready(app):
17-
app.db.__enter__()
17+
app.db.connect()
1818

1919
@app.when
2020
def shutdown(app):
21-
app.db.__exit__(None, None, None)
21+
app.db.disconnect()
2222

2323
app.db = db

yhttp/ext/sqlalchemy/orm.py

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,70 @@
11
import functools
22

33
from sqlalchemy import create_engine
4-
from sqlalchemy.orm import sessionmaker, close_all_sessions, Session
4+
from sqlalchemy.orm import sessionmaker, close_all_sessions, Session, \
5+
scoped_session
56
from yhttp.core import HTTPStatus
67

78

8-
class DatabaseManager:
9-
def __init__(self, app, basemodel):
10-
self.app = app
9+
class ORM:
10+
def __init__(self, basemodel, url=None):
11+
self.url = url
1112
self.engine = None
12-
self.sessionfactory = sessionmaker()
1313
self.basemodel = basemodel
14+
self.session = scoped_session(sessionmaker())
1415

15-
def __enter__(self) -> sessionmaker:
16-
if self.engine is None:
17-
if 'db' not in self.app.settings:
18-
raise ValueError(
19-
'Please provide db.url configuration entry, for example: '
20-
'postgresql://:@/dbname'
21-
)
22-
23-
self.engine = create_engine(
24-
self.app.settings.db.url,
25-
isolation_level='REPEATABLE READ'
26-
)
16+
def copy(self, url=None):
17+
return ORM(self.basemodel, url=url or self.app.settings.db.url)
2718

28-
self.sessionfactory.configure(bind=self.engine)
29-
return self.sessionfactory
19+
def create_objects(self):
20+
return self.basemodel.metadata.create_all(self.engine)
3021

31-
def __exit__(self, exc_type, exc_value, traceback):
22+
def connect(self, url=None):
23+
u = url or self.url
24+
assert self.engine is None
25+
assert u is not None
26+
27+
self.engine = create_engine(u, isolation_level='REPEATABLE READ')
28+
self.session.configure(bind=self.engine)
29+
30+
def disconnect(self):
3231
close_all_sessions()
3332
self.engine.dispose()
33+
self.engine = None
3434

35-
def create_objects(self):
36-
return self.basemodel.metadata.create_all(self.engine)
35+
def __enter__(self) -> Session:
36+
self.connect()
37+
return self
38+
39+
def __exit__(self, exc_type, exc_value, traceback):
40+
self.disconnect()
3741

38-
def session(self) -> Session:
39-
return self.sessionfactory.begin()
42+
43+
class ApplicationORM(ORM):
44+
def __init__(self, basemodel, app):
45+
self.app = app
46+
super().__init__(basemodel)
47+
48+
def connect(self, url=None):
49+
if 'db' not in self.app.settings or 'url' not in self.app.settings.db:
50+
raise ValueError(
51+
'Please provide db.url configuration entry, for example: '
52+
'postgresql://:@/dbname'
53+
)
54+
55+
return super().connect(url=url or self.app.settings.db.url)
4056

4157
def __call__(self, handler):
4258
@functools.wraps(handler)
43-
def outter(req, *a, **kw):
44-
with self.session() as session:
45-
req.dbsession = session
46-
try:
47-
return handler(req, *a, **kw)
48-
except HTTPStatus as ex:
49-
if ex.keepheaders:
50-
return ex
51-
52-
raise
53-
finally:
54-
del req.dbsession
59+
def outter(*a, **kw):
60+
try:
61+
return handler(*a, **kw)
62+
except HTTPStatus as ex:
63+
if ex.keepheaders:
64+
return ex
65+
66+
raise
67+
finally:
68+
self.session.reset()
5569

5670
return outter

0 commit comments

Comments
 (0)