diff --git a/.travis.yml b/.travis.yml index b8504fa..5421ff5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ install: before_script: - mysql -e 'CREATE DATABASE IF NOT EXISTS test;' - psql -c 'create database test;' -U postgres -script: python tests/sqltests.py +script: python tests/sql.py jobs: include: - stage: deploy diff --git a/setup.py b/setup.py index 1c49faf..ea29c0f 100644 --- a/setup.py +++ b/setup.py @@ -10,11 +10,11 @@ "Topic :: Software Development :: Libraries :: Python Modules" ], description="CS50 library for Python", - install_requires=["SQLAlchemy", "sqlparse"], + install_requires=["SQLAlchemy", "sqlparse", "termcolor"], keywords="cs50", name="cs50", package_dir={"": "src"}, packages=["cs50"], url="https://github.com/cs50/python-cs50", - version="2.2.0" + version="2.3.0" ) diff --git a/src/cs50/__init__.py b/src/cs50/__init__.py index 5a02d67..447dd23 100644 --- a/src/cs50/__init__.py +++ b/src/cs50/__init__.py @@ -1,7 +1,13 @@ -import imp import sys -from .cs50 import * +from .cs50 import eprint, get_char, get_float, get_int, get_string +try: + from .cs50 import get_long +except: + pass + +from . import flask + class CustomImporter(object): """ @@ -11,14 +17,18 @@ class CustomImporter(object): http://xion.org.pl/2012/05/06/hacking-python-imports/ http://dangerontheranger.blogspot.com/2012/07/how-to-use-sysmetapath-with-python.html """ + def find_module(self, fullname, path=None): if fullname == "cs50.SQL": return self return None + def load_module(self, name): if name in sys.modules: return sys.modules[name] from .sql import SQL sys.modules[name] = SQL return SQL + + sys.meta_path.append(CustomImporter()) diff --git a/src/cs50/cs50.py b/src/cs50/cs50.py index b03cf04..190aa96 100644 --- a/src/cs50/cs50.py +++ b/src/cs50/cs50.py @@ -1,14 +1,22 @@ from __future__ import print_function + import inspect import re import sys +from distutils.sysconfig import get_python_lib +from os.path import abspath, join +from termcolor import colored +from traceback import extract_tb, format_list, format_exception_only, format_exception + + class flushfile(): """ Disable buffering for standard output and standard error. http://stackoverflow.com/a/231216 """ + def __init__(self, f): self.f = f @@ -18,9 +26,12 @@ def __getattr__(self, name): def write(self, x): self.f.write(x) self.f.flush() + + sys.stderr = flushfile(sys.stderr) sys.stdout = flushfile(sys.stdout) + def eprint(*args, **kwargs): """ Print an error message to standard error, prefixing it with @@ -32,6 +43,32 @@ def eprint(*args, **kwargs): print("{}:{}: ".format(filename, lineno), end="") print(*args, end=end, file=sys.stderr, sep=sep) + +def formatException(type, value, tb): + """ + Format traceback, darkening entries from global site-packages directories + and user-specific site-packages directory. + + https://stackoverflow.com/a/46071447/5156190 + """ + + # Absolute paths to site-packages + packages = tuple(join(abspath(p), "") for p in sys.path[1:]) + + # Darken lines referring to files in site-packages + lines = [] + for line in format_exception(type, value, tb): + matches = re.search(r"^ File \"([^\"]+)\", line \d+, in .+", line) + if matches and matches.group(1).startswith(packages): + lines += colored(line, attrs=["dark"]) + else: + lines += line + return "".join(lines).rstrip() + + +sys.excepthook = lambda type, value, tb: print(formatException(type, value, tb), file=sys.stderr) + + def get_char(prompt=None): """ Read a line of text from standard input and return the equivalent char; @@ -49,6 +86,7 @@ def get_char(prompt=None): if prompt is None: print("Retry: ", end="") + def get_float(prompt=None): """ Read a line of text from standard input and return the equivalent float @@ -69,6 +107,7 @@ def get_float(prompt=None): if prompt is None: print("Retry: ", end="") + def get_int(prompt=None): """ Read a line of text from standard input and return the equivalent int; @@ -76,13 +115,13 @@ def get_int(prompt=None): can't be read, return None. """ while True: - s = get_string(prompt); + s = get_string(prompt) if s is None: return None if re.search(r"^[+-]?\d+$", s): try: i = int(s, 10) - if type(i) is int: # could become long in Python 2 + if type(i) is int: # could become long in Python 2 return i except ValueError: pass @@ -91,6 +130,7 @@ def get_int(prompt=None): if prompt is None: print("Retry: ", end="") + if sys.version_info.major != 3: def get_long(prompt=None): """ @@ -112,6 +152,7 @@ def get_long(prompt=None): if prompt is None: print("Retry: ", end="") + def get_string(prompt=None): """ Read a line of text from standard input and return it as a string, diff --git a/src/cs50/flask.py b/src/cs50/flask.py new file mode 100644 index 0000000..e55bf22 --- /dev/null +++ b/src/cs50/flask.py @@ -0,0 +1,33 @@ +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 0.12 (in case logging changes in 0.13) + version = StrictVersion(get_distribution("flask").version) + assert version >= StrictVersion("0.10") and version < StrictVersion("0.13") + + # Get default logger + import flask.logging + f = flask.logging.create_logger + + def create_logger(app): + """Wrap default logger""" + + # Create default logger + logger = f(app) + + # Reformat default logger's exceptions + # https://docs.python.org/3/library/logging.html#logging.Formatter.formatException + for handler in logger.handlers: + handler.formatter.formatException = lambda exc_info: formatException(*exc_info) + return logger + + # Replace default logger + flask.logging.create_logger = create_logger + +except: + pass diff --git a/tests/flask/application.py b/tests/flask/application.py new file mode 100644 index 0000000..643dada --- /dev/null +++ b/tests/flask/application.py @@ -0,0 +1,13 @@ +import requests +from flask import Flask, render_template + +import cs50 + +app = Flask(__name__) + +@app.route("/") +def index(): + def f(): + res = requests.get("cs50.harvard.edu") + f() + return render_template("index.html") diff --git a/tests/flask/requirements.txt b/tests/flask/requirements.txt new file mode 100644 index 0000000..7d0c101 --- /dev/null +++ b/tests/flask/requirements.txt @@ -0,0 +1,2 @@ +cs50 +Flask diff --git a/tests/flask/templates/error.html b/tests/flask/templates/error.html new file mode 100644 index 0000000..3302040 --- /dev/null +++ b/tests/flask/templates/error.html @@ -0,0 +1,10 @@ + + + +
+