Skip to content

Commit 7bf7e16

Browse files
author
Kareem Zidane
committed
avoid using sessions
1 parent 05a4d9d commit 7bf7e16

File tree

9 files changed

+174
-420
lines changed

9 files changed

+174
-420
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"Topic :: Software Development :: Libraries :: Python Modules"
1111
],
1212
description="CS50 library for Python",
13-
install_requires=["Flask>=1.0", "SQLAlchemy", "sqlparse", "termcolor"],
13+
install_requires=["Flask>=1.0", "SQLAlchemy<2", "sqlparse", "termcolor"],
1414
keywords="cs50",
1515
name="cs50",
1616
package_dir={"": "src"},

src/cs50/_engine.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import threading
2+
3+
from ._engine_util import create_engine
4+
5+
6+
thread_local_data = threading.local()
7+
8+
9+
class Engine:
10+
"""Wraps a SQLAlchemy engine.
11+
"""
12+
13+
def __init__(self, url):
14+
self._engine = create_engine(url)
15+
16+
def get_transaction_connection(self):
17+
"""
18+
:returns: A new connection with autocommit disabled (to be used for transactions).
19+
"""
20+
21+
_thread_local_connections()[self] = self._engine.connect().execution_options(
22+
autocommit=False)
23+
return self.get_existing_transaction_connection()
24+
25+
def get_connection(self):
26+
"""
27+
:returns: A new connection with autocommit enabled
28+
"""
29+
30+
return self._engine.connect().execution_options(autocommit=True)
31+
32+
def get_existing_transaction_connection(self):
33+
"""
34+
:returns: The transaction connection bound to this Engine instance, if one exists, or None.
35+
"""
36+
37+
return _thread_local_connections().get(self)
38+
39+
def close_transaction_connection(self):
40+
"""Closes the transaction connection bound to this Engine instance, if one exists and
41+
removes it.
42+
"""
43+
44+
connection = self.get_existing_transaction_connection()
45+
if connection:
46+
connection.close()
47+
del _thread_local_connections()[self]
48+
49+
def is_postgres(self):
50+
return self._engine.dialect.name in {"postgres", "postgresql"}
51+
52+
def __getattr__(self, attr):
53+
return getattr(self._engine, attr)
54+
55+
def _thread_local_connections():
56+
"""
57+
:returns: A thread local dict to keep track of transaction connection. If one does not exist,
58+
creates one.
59+
"""
60+
61+
try:
62+
connections = thread_local_data.connections
63+
except AttributeError:
64+
connections = thread_local_data.connections = {}
65+
66+
return connections

src/cs50/_engine_util.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Utility functions used by _session.py.
2+
"""
3+
4+
import os
5+
import sqlite3
6+
7+
import sqlalchemy
8+
9+
sqlite_url_prefix = "sqlite:///"
10+
11+
12+
def create_engine(url, **kwargs):
13+
"""Creates a new SQLAlchemy engine. If ``url`` is a URL for a SQLite database, makes sure that
14+
the SQLite file exits and enables foreign key constraints.
15+
"""
16+
17+
try:
18+
engine = sqlalchemy.create_engine(url, **kwargs)
19+
except sqlalchemy.exc.ArgumentError:
20+
raise RuntimeError(f"invalid URL: {url}") from None
21+
22+
if _is_sqlite_url(url):
23+
_assert_sqlite_file_exists(url)
24+
sqlalchemy.event.listen(engine, "connect", _enable_sqlite_foreign_key_constraints)
25+
26+
return engine
27+
28+
def _is_sqlite_url(url):
29+
return url.startswith(sqlite_url_prefix)
30+
31+
32+
def _assert_sqlite_file_exists(url):
33+
path = url[len(sqlite_url_prefix):]
34+
if not os.path.exists(path):
35+
raise RuntimeError(f"does not exist: {path}")
36+
if not os.path.isfile(path):
37+
raise RuntimeError(f"not a file: {path}")
38+
39+
40+
def _enable_sqlite_foreign_key_constraints(dbapi_connection, _):
41+
cursor = dbapi_connection.cursor()
42+
cursor.execute("PRAGMA foreign_keys=ON")
43+
cursor.close()

src/cs50/_session.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

src/cs50/_session_util.py

Lines changed: 0 additions & 68 deletions
This file was deleted.

src/cs50/_sql_util.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import decimal
66
import warnings
77

8+
import sqlalchemy
9+
810

911
def process_select_result(result):
1012
"""Converts a SQLAlchemy result to a ``list`` of ``dict`` objects, each of which represents a
@@ -36,3 +38,14 @@ def raise_errors_for_warnings():
3638
with warnings.catch_warnings():
3739
warnings.simplefilter("error")
3840
yield
41+
42+
43+
def postgres_lastval(connection):
44+
"""
45+
:returns: The ID of the last inserted row, if defined in this session, or None
46+
"""
47+
48+
try:
49+
return connection.execute("SELECT LASTVAL()").first()[0]
50+
except sqlalchemy.exc.OperationalError:
51+
return None

src/cs50/_statement.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __init__(self, sql_sanitizer, sql, *args, **kwargs):
6464
self._paramstyle = self._get_paramstyle()
6565
self._placeholders = self._get_placeholders()
6666
self._substitute_markers_with_escaped_params()
67-
self._escape_verbatim_colons()
67+
# self._escape_verbatim_colons()
6868

6969
def _get_escaped_args(self, args):
7070
return [self._sql_sanitizer.escape(arg) for arg in args]

0 commit comments

Comments
 (0)