Skip to content

Commit d37e82b

Browse files
committed
2.3.1
1 parent dd831a1 commit d37e82b

File tree

15 files changed

+635
-0
lines changed

15 files changed

+635
-0
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# CS50 Library for Python
2+
3+
[![Build Status](https://travis-ci.org/cs50/python-cs50.svg?branch=master)](https://travis-ci.org/cs50/python-cs50)
4+
5+
Supports Python 2 and Python 3.
6+
7+
## Installation
8+
9+
```
10+
pip install cs50
11+
```
12+
13+
## Usage
14+
15+
import cs50
16+
17+
...
18+
19+
c = cs50.get_char();
20+
f = cs50.get_float();
21+
i = cs50.get_int();
22+
l = cs50.get_long(); # Python 2 only
23+
s = cs50.get_string();
24+
25+
# References
26+
27+
- https://github.com/ronsavage/SQL/blob/master/sql-92.bnf#L19-L72
28+
29+
## TODO
30+
* Add targets for `pacman`, `rpm`.
31+
* Add tests.

setup.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from setuptools import setup
2+
3+
setup(
4+
author="CS50",
5+
author_email="[email protected]",
6+
classifiers=[
7+
"Intended Audience :: Developers",
8+
"Programming Language :: Python",
9+
"Programming Language :: Python :: 3",
10+
"Topic :: Software Development :: Libraries :: Python Modules"
11+
],
12+
description="CS50 library for Python",
13+
install_requires=["SQLAlchemy", "sqlparse", "termcolor"],
14+
keywords="cs50",
15+
name="cs50",
16+
package_dir={"": "src"},
17+
packages=["cs50"],
18+
url="https://github.com/cs50/python-cs50",
19+
version="2.3.1"
20+
)

src/cs50/__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import sys
2+
3+
from .cs50 import eprint, get_char, get_float, get_int, get_string
4+
try:
5+
from .cs50 import get_long
6+
except:
7+
pass
8+
9+
from . import flask
10+
11+
12+
class CustomImporter(object):
13+
"""
14+
Import cs50.SQL lazily so that rest of library can be used without SQLAlchemy installed.
15+
16+
https://docs.python.org/3/library/imp.html
17+
http://xion.org.pl/2012/05/06/hacking-python-imports/
18+
http://dangerontheranger.blogspot.com/2012/07/how-to-use-sysmetapath-with-python.html
19+
"""
20+
21+
def find_module(self, fullname, path=None):
22+
if fullname == "cs50.SQL":
23+
return self
24+
return None
25+
26+
def load_module(self, name):
27+
if name in sys.modules:
28+
return sys.modules[name]
29+
from .sql import SQL
30+
sys.modules[name] = SQL
31+
return SQL
32+
33+
34+
sys.meta_path.append(CustomImporter())

src/cs50/cs50.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
from __future__ import print_function
2+
3+
import inspect
4+
import re
5+
import sys
6+
7+
from distutils.sysconfig import get_python_lib
8+
from os.path import abspath, join
9+
from termcolor import colored
10+
from traceback import extract_tb, format_list, format_exception_only, format_exception
11+
12+
13+
class flushfile():
14+
"""
15+
Disable buffering for standard output and standard error.
16+
17+
http://stackoverflow.com/a/231216
18+
"""
19+
20+
def __init__(self, f):
21+
self.f = f
22+
23+
def __getattr__(self, name):
24+
return object.__getattribute__(self.f, name)
25+
26+
def write(self, x):
27+
self.f.write(x)
28+
self.f.flush()
29+
30+
31+
sys.stderr = flushfile(sys.stderr)
32+
sys.stdout = flushfile(sys.stdout)
33+
34+
35+
def eprint(*args, **kwargs):
36+
"""
37+
Print an error message to standard error, prefixing it with
38+
file name and line number from which method was called.
39+
"""
40+
end = kwargs.get("end", "\n")
41+
sep = kwargs.get("sep", " ")
42+
(filename, lineno) = inspect.stack()[1][1:3]
43+
print("{}:{}: ".format(filename, lineno), end="")
44+
print(*args, end=end, file=sys.stderr, sep=sep)
45+
46+
47+
def formatException(type, value, tb):
48+
"""
49+
Format traceback, darkening entries from global site-packages directories
50+
and user-specific site-packages directory.
51+
52+
https://stackoverflow.com/a/46071447/5156190
53+
"""
54+
55+
# Absolute paths to site-packages
56+
packages = tuple(join(abspath(p), "") for p in sys.path[1:])
57+
58+
# Highlight lines not referring to files in site-packages
59+
lines = []
60+
for line in format_exception(type, value, tb):
61+
matches = re.search(r"^ File \"([^\"]+)\", line \d+, in .+", line)
62+
if matches and matches.group(1).startswith(packages):
63+
lines += line
64+
else:
65+
matches = re.search(r"^(\s*)(.*?)(\s*)$", line, re.DOTALL)
66+
lines.append(matches.group(1) + colored(matches.group(2), "yellow") + matches.group(3))
67+
return "".join(lines).rstrip()
68+
69+
70+
sys.excepthook = lambda type, value, tb: print(formatException(type, value, tb), file=sys.stderr)
71+
72+
73+
def get_char(prompt=None):
74+
"""
75+
Read a line of text from standard input and return the equivalent char;
76+
if text is not a single char, user is prompted to retry. If line can't
77+
be read, return None.
78+
"""
79+
while True:
80+
s = get_string(prompt)
81+
if s is None:
82+
return None
83+
if len(s) == 1:
84+
return s[0]
85+
86+
# temporarily here for backwards compatibility
87+
if prompt is None:
88+
print("Retry: ", end="")
89+
90+
91+
def get_float(prompt=None):
92+
"""
93+
Read a line of text from standard input and return the equivalent float
94+
as precisely as possible; if text does not represent a double, user is
95+
prompted to retry. If line can't be read, return None.
96+
"""
97+
while True:
98+
s = get_string(prompt)
99+
if s is None:
100+
return None
101+
if len(s) > 0 and re.search(r"^[+-]?\d*(?:\.\d*)?$", s):
102+
try:
103+
return float(s)
104+
except ValueError:
105+
pass
106+
107+
# temporarily here for backwards compatibility
108+
if prompt is None:
109+
print("Retry: ", end="")
110+
111+
112+
def get_int(prompt=None):
113+
"""
114+
Read a line of text from standard input and return the equivalent int;
115+
if text does not represent an int, user is prompted to retry. If line
116+
can't be read, return None.
117+
"""
118+
while True:
119+
s = get_string(prompt)
120+
if s is None:
121+
return None
122+
if re.search(r"^[+-]?\d+$", s):
123+
try:
124+
i = int(s, 10)
125+
if type(i) is int: # could become long in Python 2
126+
return i
127+
except ValueError:
128+
pass
129+
130+
# temporarily here for backwards compatibility
131+
if prompt is None:
132+
print("Retry: ", end="")
133+
134+
135+
if sys.version_info.major != 3:
136+
def get_long(prompt=None):
137+
"""
138+
Read a line of text from standard input and return the equivalent long;
139+
if text does not represent a long, user is prompted to retry. If line
140+
can't be read, return None.
141+
"""
142+
while True:
143+
s = get_string(prompt)
144+
if s is None:
145+
return None
146+
if re.search(r"^[+-]?\d+$", s):
147+
try:
148+
return long(s, 10)
149+
except ValueError:
150+
pass
151+
152+
# temporarily here for backwards compatibility
153+
if prompt is None:
154+
print("Retry: ", end="")
155+
156+
157+
def get_string(prompt=None):
158+
"""
159+
Read a line of text from standard input and return it as a string,
160+
sans trailing line ending. Supports CR (\r), LF (\n), and CRLF (\r\n)
161+
as line endings. If user inputs only a line ending, returns "", not None.
162+
Returns None upon error or no input whatsoever (i.e., just EOF). Exits
163+
from Python altogether on SIGINT.
164+
"""
165+
try:
166+
if prompt is not None:
167+
print(prompt, end="")
168+
s = sys.stdin.readline()
169+
if not s:
170+
return None
171+
return re.sub(r"(?:\r|\r\n|\n)$", "", s)
172+
except KeyboardInterrupt:
173+
sys.exit("")
174+
except ValueError:
175+
return None

src/cs50/flask.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from distutils.version import StrictVersion
2+
from pkg_resources import get_distribution
3+
4+
from .cs50 import formatException
5+
6+
# Try to monkey-patch Flask, if installed
7+
try:
8+
9+
# Only patch 0.12 (in case logging changes in 0.13)
10+
version = StrictVersion(get_distribution("flask").version)
11+
assert version >= StrictVersion("0.10") and version < StrictVersion("0.13")
12+
13+
# Get default logger
14+
import flask.logging
15+
f = flask.logging.create_logger
16+
17+
def create_logger(app):
18+
"""Wrap default logger"""
19+
20+
# Create default logger
21+
logger = f(app)
22+
23+
# Reformat default logger's exceptions
24+
# https://docs.python.org/3/library/logging.html#logging.Formatter.formatException
25+
for handler in logger.handlers:
26+
handler.formatter.formatException = lambda exc_info: formatException(*exc_info)
27+
return logger
28+
29+
# Replace default logger
30+
flask.logging.create_logger = create_logger
31+
32+
except:
33+
pass

0 commit comments

Comments
 (0)