Skip to content

lazily importing flask #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 6, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions src/cs50/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
import logging
import os
import sys

try:

# Save student's sys.path
_path = sys.path[:]

# In case student has files that shadow packages
sys.path = [p for p in sys.path if p not in ("", os.getcwd())]
# Disable cs50 logger by default
logging.getLogger("cs50").disabled = True

# Import cs50_*
from .cs50 import get_char, get_float, get_int, get_string
# In case student has files that shadow packages
for p in ("", os.getcwd()):
try:
from .cs50 import get_long
except ImportError:
sys.path.remove(p)
except ValueError:
pass

# Replace Flask's logger
from . import flask

# Wrap SQLAlchemy
from .sql import SQL
# Import cs50_*
from .cs50 import get_char, get_float, get_int, get_string
try:
from .cs50 import get_long
except ImportError:
pass

finally:
# Hook into flask importing
from . import flask

# Restore student's sys.path (just in case library raised an exception that caller caught)
sys.path = _path
# Wrap SQLAlchemy
from .sql import SQL
100 changes: 40 additions & 60 deletions src/cs50/flask.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,40 @@
import logging

from distutils.version import StrictVersion
from pkg_resources import get_distribution

from .cs50 import _formatException

# Try to monkey-patch Flask, if installed
try:

# Only patch >= 1.0
_version = StrictVersion(get_distribution("flask").version)
assert _version >= StrictVersion("1.0")

# Reformat logger's exceptions
# http://flask.pocoo.org/docs/1.0/logging/
# https://docs.python.org/3/library/logging.html#logging.Formatter.formatException
try:
import flask.logging
flask.logging.default_handler.formatter.formatException = lambda exc_info: _formatException(*exc_info)
except Exception:
pass

# Enable logging when Flask is in use,
# monkey-patching own SQL module, which shouldn't need to know about Flask
logging.getLogger("cs50").disabled = True
try:
import flask
from .sql import SQL
except ImportError:
pass
else:
_execute_before = SQL.execute
def _execute_after(*args, **kwargs):
disabled = logging.getLogger("cs50").disabled
if flask.current_app:
logging.getLogger("cs50").disabled = False
try:
return _execute_before(*args, **kwargs)
finally:
logging.getLogger("cs50").disabled = disabled
SQL.execute = _execute_after

# When behind CS50 IDE's proxy, ensure that flask.redirect doesn't redirect from HTTPS to HTTP
# https://werkzeug.palletsprojects.com/en/0.15.x/middleware/proxy_fix/#module-werkzeug.middleware.proxy_fix
from os import getenv
if getenv("CS50_IDE_TYPE") == "online":
try:
import flask
from werkzeug.middleware.proxy_fix import ProxyFix
_flask_init_before = flask.Flask.__init__
def _flask_init_after(self, *args, **kwargs):
_flask_init_before(self, *args, **kwargs)
self.wsgi_app = ProxyFix(self.wsgi_app, x_proto=1)
flask.Flask.__init__ = _flask_init_after
except:
pass

except Exception:
pass
import os
import pkgutil
import sys

def _wrap_flask(f):
if f is None:
return

from distutils.version import StrictVersion
from .cs50 import _formatException

if f.__version__ < StrictVersion("1.0"):
return

f.logging.default_handler.formatter.formatException = lambda exc_info: _formatException(*exc_info)

if os.getenv("CS50_IDE_TYPE") == "online":
from werkzeug.middleware.proxy_fix import ProxyFix
_flask_init_before = f.Flask.__init__
def _flask_init_after(self, *args, **kwargs):
_flask_init_before(self, *args, **kwargs)
self.wsgi_app = ProxyFix(self.wsgi_app, x_proto=1)
f.Flask.__init__ = _flask_init_after


# Flask was imported before cs50
if "flask" in sys.modules:
_wrap_flask(sys.modules["flask"])

# Flask wasn't imported
else:
flask_loader = pkgutil.get_loader('flask')
if flask_loader:
_exec_module_before = flask_loader.exec_module

def _exec_module_after(*args, **kwargs):
_exec_module_before(*args, **kwargs)
_wrap_flask(sys.modules["flask"])

flask_loader.exec_module = _exec_module_after
23 changes: 23 additions & 0 deletions src/cs50/sql.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
def _enable_logging(f):
import logging
import functools

@functools.wraps(f)
def decorator(*args, **kwargs):
try:
import flask
except ModuleNotFoundError:
return f(*args, **kwargs)

disabled = logging.getLogger("cs50").disabled
if flask.current_app:
logging.getLogger("cs50").disabled = False
try:
return f(*args, **kwargs)
finally:
logging.getLogger("cs50").disabled = disabled

return decorator


class SQL(object):
"""Wrap SQLAlchemy to provide a simple SQL API."""

@@ -64,6 +86,7 @@ def connect(dbapi_connection, connection_record):
finally:
self._logger.disabled = disabled

@_enable_logging
def execute(self, sql, *args, **kwargs):
"""Execute a SQL statement."""