├── README ├── DEONE ├── __local ├── admin.ico ├── deone.ico ├── README.txt ├── demo.py ├── auth.py ├── stock.py ├── market.py ├── client.py ├── trading.py ├── querytable.py ├── sector.py ├── ohlc.py ├── deone.py ├── main.py └── admin.py ├── .gitignore └── appspot ├── restlet ├── __init__.py ├── auth.py ├── jsonutil.py ├── parsutil.py ├── restutil.py └── intgutil.py ├── suas ├── __init__.py ├── users.py ├── signedcookie.py ├── auth_handlers.py └── session.py ├── app.yaml ├── models.py ├── index.yaml ├── simplejson ├── tool.py ├── scanner.py ├── decoder.py ├── encoder.py └── __init__.py └── main.py /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DEONE/__local: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /appspot/restlet/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /appspot/suas/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DEONE/admin.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/deone/master/DEONE/admin.ico -------------------------------------------------------------------------------- /DEONE/deone.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/deone/master/DEONE/deone.ico -------------------------------------------------------------------------------- /appspot/app.yaml: -------------------------------------------------------------------------------- 1 | application: de-one 2 | version: 2 3 | runtime: python 4 | api_version: 1 5 | 6 | handlers: 7 | - url: .* 8 | script: main.py 9 | -------------------------------------------------------------------------------- /appspot/models.py: -------------------------------------------------------------------------------- 1 | """A toy-level example of a data model in Google Appengine DB terms. 2 | """ 3 | import logging 4 | from google.appengine.ext import db 5 | from restlet import restutil 6 | 7 | class Deone(db.Model): 8 | data = db.TextProperty() 9 | 10 | restutil.decorateModuleNamed(__name__) 11 | logging.info('Models in %r decorated', __name__) 12 | -------------------------------------------------------------------------------- /DEONE/README.txt: -------------------------------------------------------------------------------- 1 | #C:\vpy\DEONE>python c:\vpy\pyinstaller\Configure.py --upx-dir=c:\vpy\upx305w 2 | 3 | #c:\vpy\DEONE>python C:\vpy\pyinstaller\Makespec.py main.py -X -F -w -o c:\vpy\DEONE\build --icon=deone.ico 4 | wrote c:\vpy\DEONE\main.spec 5 | now run Build.py to build the executable 6 | 7 | #C:\vpy\DEONE>python c:\vpy\pyinstaller\Build.py c:\vpy\DEONE\build\main.spec -o c:\vpy\DEONE\build 8 | -------------------------------------------------------------------------------- /appspot/index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | 3 | # AUTOGENERATED 4 | 5 | # This index.yaml is automatically updated whenever the dev_appserver 6 | # detects that a new type of query is run. If you want to manage the 7 | # index.yaml file manually, remove the above marker line (the line 8 | # saying "# AUTOGENERATED"). If you want to manage some indexes 9 | # manually, move them above the marker line. The index.yaml file is 10 | # automatically uploaded to the admin console when you next deploy 11 | # your application using appcfg.py. 12 | -------------------------------------------------------------------------------- /DEONE/demo.py: -------------------------------------------------------------------------------- 1 | import sys,random 2 | from client import App3Client 3 | import simplejson 4 | 5 | c = App3Client('de-one.appspot.com', 'test', 'test') 6 | #c = App3Client('localhost:8080', 'test', 'test') 7 | #c = App3Client('localhost:8080', 'test', 'wrong_password') 8 | 9 | #Deone 10 | s = c.list('Deone') 11 | print(s) 12 | if s is not None: 13 | items = simplejson.loads(s) 14 | for item in items: 15 | id = item['id'] 16 | print(id) 17 | #print(c.get('Deone', id)) 18 | c.delete('Deone', id) 19 | #c.post('Deone', {"data": "1234567890123"}) 20 | -------------------------------------------------------------------------------- /appspot/simplejson/tool.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Using simplejson from the shell to validate and 3 | pretty-print:: 4 | 5 | $ echo '{"json":"obj"}' | python -msimplejson 6 | { 7 | "json": "obj" 8 | } 9 | $ echo '{ 1.2:3.4}' | python -msimplejson 10 | Expecting property name: line 1 column 2 (char 2) 11 | 12 | Note that the JSON produced by this module's default settings 13 | is a subset of YAML, so it may be used as a serializer for that as well. 14 | """ 15 | import simplejson 16 | 17 | # 18 | # Pretty printer: 19 | # curl http://mochikit.com/examples/ajax_tables/domains.json | python -msimplejson.tool 20 | # 21 | 22 | def main(): 23 | import sys 24 | if len(sys.argv) == 1: 25 | infile = sys.stdin 26 | outfile = sys.stdout 27 | elif len(sys.argv) == 2: 28 | infile = open(sys.argv[1], 'rb') 29 | outfile = sys.stdout 30 | elif len(sys.argv) == 3: 31 | infile = open(sys.argv[1], 'rb') 32 | outfile = open(sys.argv[2], 'wb') 33 | else: 34 | raise SystemExit("%s [infile [outfile]]" % (sys.argv[0],)) 35 | try: 36 | obj = simplejson.load(infile) 37 | except ValueError, e: 38 | raise SystemExit(e) 39 | simplejson.dump(obj, outfile, sort_keys=True, indent=4) 40 | outfile.write('\n') 41 | 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /appspot/main.py: -------------------------------------------------------------------------------- 1 | import wsgiref.handlers 2 | from google.appengine.ext import webapp 3 | 4 | from google.appengine.ext.webapp import util 5 | 6 | from google.appengine.ext.webapp import template 7 | 8 | from suas import session, auth_handlers 9 | 10 | # Set up our REST resources 11 | import models 12 | 13 | HOME_VIEW = template.Template(""" 14 | Home 15 | 16 |

Demo app for DEONE

17 | {% if session.flash_msg %} 18 |

{{ session.flash_msg }}

19 | {% endif %} 20 | {% if session.user %} 21 |

Logged in as {{ session.user }}.

22 |

Log out

23 | {% else %} 24 |

Log in

25 |

Sign up

26 | {% endif %} 27 | 28 | """) 29 | 30 | class HomeHandler(session.RequestHandler): 31 | def get(self): 32 | ctx = template.Context({"session": self.session}) 33 | self.response.out.write(HOME_VIEW.render(ctx)) 34 | 35 | from restlet.intgutil import JsonRestHelper 36 | 37 | helper = JsonRestHelper() 38 | 39 | class CrudRestHandler(webapp.RequestHandler): 40 | def __init__(self, *a, **k): 41 | webapp.RequestHandler.__init__(self, *a, **k) 42 | helper.hookup(self) 43 | ROUTES = [('/', HomeHandler), ('/(rest)/.*', CrudRestHandler)] + auth_handlers.ROUTES 44 | 45 | #from restlet.handler import CrudRestHandler 46 | #ROUTES = [('/home', HomeHandler), ('/', CrudRestHandler)] + auth_handlers.ROUTES 47 | 48 | def main(): 49 | import logging 50 | logging.basicConfig(level=logging.DEBUG,) 51 | 52 | # Create the AppEngine WSGI Application 53 | application = webapp.WSGIApplication(ROUTES, debug=True) 54 | 55 | wsgiref.handlers.CGIHandler().run(application) 56 | 57 | if __name__ == '__main__': 58 | main() 59 | -------------------------------------------------------------------------------- /appspot/suas/users.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python2.5 2 | #---------------------------- 3 | # Datastore models for user & signup 4 | #---------------------------- 5 | 6 | from base64 import b64encode as b64 7 | from hashlib import md5, sha256 8 | from random import randint 9 | from time import time 10 | 11 | from google.appengine.ext import db 12 | 13 | import logging 14 | 15 | N_SALT = 8 # length of the password salt 16 | 17 | 18 | def salt_n_hash(password, salt=None): 19 | """ 20 | Generate a salt and return in base64 encoding the hash of the 21 | password with the salt and the character '$' prepended to it. 22 | """ 23 | salt = salt or b64( ''.join(chr(randint(0, 0xff)) for _ in range(N_SALT)) ) 24 | return salt + '$' + b64( sha256(salt+password.encode("ascii")).digest() ) 25 | 26 | 27 | class User(db.Model): 28 | nickname = db.StringProperty(required=True) 29 | email = db.EmailProperty(required=True) 30 | pwd = db.StringProperty(required=True) 31 | suspended = db.BooleanProperty(default=True) 32 | 33 | @classmethod 34 | def authenticate(klass, nickname, password): 35 | """Return an User() entity instance if password is correct""" 36 | logging.info("authenticate-- user:" + nickname + " password:" + password) 37 | 38 | user = klass.get_by_key_name(nickname) 39 | if user: 40 | n_salt = user.pwd.index('$') 41 | logging.info("user: %s password:%s suspended:%d, n_salt:%d" %(user.nickname,user.pwd,user.suspended,n_salt)) 42 | if user.pwd == salt_n_hash(password, salt=user.pwd[:n_salt]): 43 | logging.info("authenticated user") 44 | return user 45 | 46 | def __eq__(self, other): 47 | return self.nickname == other.nickname 48 | 49 | 50 | def signup_id(nickname): 51 | return md5( nickname + repr(time()) ).hexdigest() 52 | 53 | 54 | class UserSignup(db.Model): 55 | user = db.ReferenceProperty(User, required=True) 56 | date = db.DateProperty(auto_now_add=True) 57 | -------------------------------------------------------------------------------- /DEONE/auth.py: -------------------------------------------------------------------------------- 1 | import hmac, sha, base64 2 | from datetime import datetime 3 | import logging 4 | 5 | TIMEFORMAT = "%a, %d %b %Y %H:%M:%S +0000" 6 | SECRETKEY = 'deone_appspot' 7 | 8 | def generate_auth(app3_user, app3_timestamp): 9 | """ 10 | The timestamp format should be as specified in RFC 2822 and in UTC: 11 | "%a, %d %b %Y %H:%M:%S +0000" 12 | 13 | - See http://www.faqs.org/rfcs/rfc2822.html 14 | """ 15 | message = "%s\n%s" % (app3_user, app3_timestamp) 16 | 17 | auth = hmac.new( 18 | key = SECRETKEY, 19 | msg = message, 20 | digestmod = sha, 21 | ).digest() 22 | 23 | return base64.encodestring(auth).strip() 24 | 25 | def generate_timestamp(): 26 | """ 27 | Generates a timestamp in the standard format. 28 | """ 29 | return datetime.utcnow().strftime(TIMEFORMAT) 30 | 31 | def is_within_n_minutes(sent_time, n=15): 32 | """ 33 | Check whether one of our timestamps is within n minutes of 34 | now. (All times are in UTC) 35 | """ 36 | sent_time = datetime.strptime(sent_time, TIMEFORMAT) 37 | 38 | return not (datetime.utcnow() - sent_time).seconds >= n * 60 39 | 40 | def is_authorized(request): 41 | #return True 42 | if ('App3-Timestamp' not in request.headers) or ('App3-Auth' not in request.headers) or ('App3-User' not in request.headers) or ('App3-Pass' not in request.headers): 43 | logging.info("Need all of the headers to have been passed for authentication.") 44 | return False 45 | app3_timestamp = request.headers['App3-Timestamp'] 46 | app3_auth = request.headers['App3-Auth'] 47 | app3_user = request.headers['App3-User'] 48 | app3_pass = request.headers['App3-Pass'] 49 | 50 | """ 51 | Returns whether a user is authorized based on the request. 52 | """ 53 | user = User.authenticate(app3_user, app3_pass) 54 | if user and not user.suspended: 55 | logging.info("authenticated user.") 56 | else: 57 | return False 58 | 59 | # Time skew... Could be replay attack? 60 | if not is_within_n_minutes(app3_timestamp, 15): 61 | return False 62 | 63 | # Check whether we generate the same auth header as they did 64 | return app3_auth == generate_auth(app3_user, app3_timestamp) -------------------------------------------------------------------------------- /appspot/restlet/auth.py: -------------------------------------------------------------------------------- 1 | import hmac, sha, base64 2 | from datetime import datetime 3 | from suas.users import User,salt_n_hash 4 | import logging 5 | 6 | TIMEFORMAT = "%a, %d %b %Y %H:%M:%S +0000" 7 | SECRETKEY = 'deone_appspot' 8 | 9 | def generate_auth(app3_user, app3_timestamp): 10 | """ 11 | The timestamp format should be as specified in RFC 2822 and in UTC: 12 | "%a, %d %b %Y %H:%M:%S +0000" 13 | 14 | - See http://www.faqs.org/rfcs/rfc2822.html 15 | """ 16 | message = "%s\n%s" % (app3_user, app3_timestamp) 17 | 18 | auth = hmac.new( 19 | key = SECRETKEY, 20 | msg = message, 21 | digestmod = sha, 22 | ).digest() 23 | 24 | return base64.encodestring(auth).strip() 25 | 26 | def generate_timestamp(): 27 | """ 28 | Generates a timestamp in the standard format. 29 | """ 30 | return datetime.utcnow().strftime(TIMEFORMAT) 31 | 32 | def is_within_n_minutes(sent_time, n=15): 33 | """ 34 | Check whether one of our timestamps is within n minutes of 35 | now. (All times are in UTC) 36 | """ 37 | sent_time = datetime.strptime(sent_time, TIMEFORMAT) 38 | 39 | return not (datetime.utcnow() - sent_time).seconds >= n * 60 40 | 41 | def is_authorized(request): 42 | if ('App3-Timestamp' not in request.headers) or ('App3-Auth' not in request.headers) or ('App3-User' not in request.headers) or ('App3-Pass' not in request.headers): 43 | logging.info("Need all of the headers to have been passed for authentication.") 44 | return False 45 | app3_timestamp = request.headers['App3-Timestamp'] 46 | app3_auth = request.headers['App3-Auth'] 47 | app3_user = request.headers['App3-User'] 48 | app3_pass = request.headers['App3-Pass'] 49 | 50 | """ 51 | Returns whether a user is authorized based on the request. 52 | """ 53 | user = User.authenticate(app3_user, app3_pass) 54 | if user and not user.suspended: 55 | logging.info("authenticated user.") 56 | else: 57 | return False 58 | 59 | # Time skew... Could be replay attack? 60 | if not is_within_n_minutes(app3_timestamp, 15): 61 | return False 62 | 63 | # Check whether we generate the same auth header as they did 64 | return app3_auth == generate_auth(app3_user, app3_timestamp) -------------------------------------------------------------------------------- /appspot/simplejson/scanner.py: -------------------------------------------------------------------------------- 1 | """ 2 | Iterator based sre token scanner 3 | """ 4 | import re 5 | from re import VERBOSE, MULTILINE, DOTALL 6 | import sre_parse 7 | import sre_compile 8 | import sre_constants 9 | from sre_constants import BRANCH, SUBPATTERN 10 | 11 | __all__ = ['Scanner', 'pattern'] 12 | 13 | FLAGS = (VERBOSE | MULTILINE | DOTALL) 14 | 15 | class Scanner(object): 16 | def __init__(self, lexicon, flags=FLAGS): 17 | self.actions = [None] 18 | # Combine phrases into a compound pattern 19 | s = sre_parse.Pattern() 20 | s.flags = flags 21 | p = [] 22 | for idx, token in enumerate(lexicon): 23 | phrase = token.pattern 24 | try: 25 | subpattern = sre_parse.SubPattern(s, 26 | [(SUBPATTERN, (idx + 1, sre_parse.parse(phrase, flags)))]) 27 | except sre_constants.error: 28 | raise 29 | p.append(subpattern) 30 | self.actions.append(token) 31 | 32 | s.groups = len(p) + 1 # NOTE(guido): Added to make SRE validation work 33 | p = sre_parse.SubPattern(s, [(BRANCH, (None, p))]) 34 | self.scanner = sre_compile.compile(p) 35 | 36 | def iterscan(self, string, idx=0, context=None): 37 | """ 38 | Yield match, end_idx for each match 39 | """ 40 | match = self.scanner.scanner(string, idx).match 41 | actions = self.actions 42 | lastend = idx 43 | end = len(string) 44 | while True: 45 | m = match() 46 | if m is None: 47 | break 48 | matchbegin, matchend = m.span() 49 | if lastend == matchend: 50 | break 51 | action = actions[m.lastindex] 52 | if action is not None: 53 | rval, next_pos = action(m, context) 54 | if next_pos is not None and next_pos != matchend: 55 | # "fast forward" the scanner 56 | matchend = next_pos 57 | match = self.scanner.scanner(string, matchend).match 58 | yield rval, matchend 59 | lastend = matchend 60 | 61 | 62 | def pattern(pattern, flags=FLAGS): 63 | def decorator(fn): 64 | fn.pattern = pattern 65 | fn.regex = re.compile(pattern, flags) 66 | return fn 67 | return decorator -------------------------------------------------------------------------------- /DEONE/stock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4 import QtCore, QtGui 4 | from ohlc import OHLCWidget 5 | from querytable import QueryTableWidget 6 | 7 | class StockWidget(QtGui.QWidget): 8 | def __init__(self, parent): 9 | super(QtGui.QWidget, self).__init__(parent) 10 | self.setObjectName("tabStock") 11 | 12 | mainLayout = QtGui.QVBoxLayout() 13 | 14 | hLayoutQuery = QtGui.QHBoxLayout() 15 | self.label = QtGui.QLabel(self) 16 | self.label.setGeometry(QtCore.QRect(20, 10, 46, 14)) 17 | self.label.setObjectName("label") 18 | hLayoutQuery.addWidget(self.label) 19 | 20 | self.lineEdit = QtGui.QLineEdit(self) 21 | self.lineEdit.setGeometry(QtCore.QRect(80, 0, 113, 20)) 22 | self.lineEdit.setObjectName("lineEdit") 23 | hLayoutQuery.addWidget(self.lineEdit) 24 | 25 | self.pushButtonQuery = QtGui.QPushButton(self) 26 | self.pushButtonQuery.setGeometry(QtCore.QRect(210, 0, 75, 23)) 27 | self.pushButtonQuery.setObjectName("pushButtonQuery") 28 | hLayoutQuery.addWidget(self.pushButtonQuery) 29 | 30 | mainLayout.addLayout(hLayoutQuery) 31 | 32 | hLayoutInfo = QtGui.QHBoxLayout() 33 | 34 | self.ohlc = OHLCWidget(self) 35 | self.ohlc.setObjectName("OHLCWidget") 36 | hLayoutInfo.addWidget(self.ohlc) 37 | 38 | self.tbl = QueryTableWidget(self) 39 | self.tbl.setObjectName("QueryTableWidget") 40 | hLayoutInfo.addWidget(self.tbl) 41 | 42 | mainLayout.addLayout(hLayoutInfo) 43 | self.setLayout(mainLayout) 44 | 45 | self.retranslateUi() 46 | 47 | QtCore.QObject.connect(self.pushButtonQuery, QtCore.SIGNAL("pressed()"), self.doQuery) 48 | 49 | self.lineEdit.setText('1') 50 | self.doQuery() 51 | 52 | def retranslateUi(self): 53 | self.label.setText(QtGui.QApplication.translate("MainWindow", "股票代码", None, QtGui.QApplication.UnicodeUTF8)) 54 | self.pushButtonQuery.setText(QtGui.QApplication.translate("MainWindow", "查询", None, QtGui.QApplication.UnicodeUTF8)) 55 | 56 | def doQuery(self): 57 | print("called doQuery") 58 | code = str(self.lineEdit.text()) 59 | #日期 股票代码 公司名称 开盘价格 最高价格 最低价格 收盘价格 成交量 60 | self.ohlc.query(u'select 日期,开盘价格,最高价格,最低价格,收盘价格,成交量 from StockHist where 股票代码=? order by 日期 asc'.encode('utf-8'), (code,)) 61 | self.tbl.query(u'select * from StockHist where 股票代码=? order by 日期 desc'.encode('utf-8'), (code,)) 62 | pass 63 | 64 | #=============================================================================== 65 | # Example 66 | #=============================================================================== 67 | if __name__ == '__main__': 68 | import sys 69 | from PyQt4.QtGui import QMainWindow, QApplication 70 | 71 | class ApplicationWindow(QMainWindow): 72 | def __init__(self): 73 | QMainWindow.__init__(self) 74 | self.tblwidget = StockWidget(self) 75 | self.tblwidget.setFocus() 76 | self.setCentralWidget(self.tblwidget) 77 | 78 | app = QApplication(sys.argv) 79 | win = ApplicationWindow() 80 | win.show() 81 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /DEONE/market.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4 import QtCore, QtGui 4 | 5 | from ohlc import OHLCWidget 6 | from querytable import QueryTableWidget 7 | 8 | class MarketWidget(QtGui.QWidget): 9 | def __init__(self, parent): 10 | super(QtGui.QWidget, self).__init__(parent) 11 | 12 | self.setObjectName("tabMarket") 13 | 14 | mainLayout = QtGui.QVBoxLayout() 15 | 16 | hLayoutQuery = QtGui.QHBoxLayout() 17 | self.pushButtonMarketSH = QtGui.QPushButton(self) 18 | #self.pushButtonMarketSH.setGeometry(QtCore.QRect(50, 5, 75, 23)) 19 | self.pushButtonMarketSH.setObjectName("pushButtonMarketSH") 20 | hLayoutQuery.addWidget(self.pushButtonMarketSH) 21 | 22 | self.pushButtonMarketSZ = QtGui.QPushButton(self) 23 | #self.pushButtonMarketSZ.setGeometry(QtCore.QRect(200, 5, 75, 23)) 24 | self.pushButtonMarketSZ.setObjectName("pushButtonMarketSZ") 25 | hLayoutQuery.addWidget(self.pushButtonMarketSZ) 26 | 27 | mainLayout.addLayout(hLayoutQuery) 28 | 29 | hLayoutInfo = QtGui.QHBoxLayout() 30 | self.ohlc = OHLCWidget(self) 31 | self.ohlc.setObjectName("OHLCWidget") 32 | hLayoutInfo.addWidget(self.ohlc) 33 | 34 | self.tbl = QueryTableWidget(self) 35 | self.tbl.setObjectName("QueryTableWidget") 36 | hLayoutInfo.addWidget(self.tbl) 37 | 38 | mainLayout.addLayout(hLayoutInfo) 39 | self.setLayout(mainLayout) 40 | 41 | self.retranslateUi() 42 | 43 | QtCore.QObject.connect(self.pushButtonMarketSH, QtCore.SIGNAL("pressed()"), self.doQueryMarketSH) 44 | QtCore.QObject.connect(self.pushButtonMarketSZ, QtCore.SIGNAL("pressed()"), self.doQueryMarketSZ) 45 | 46 | self.doQueryMarketSH() 47 | 48 | def retranslateUi(self): 49 | self.pushButtonMarketSZ.setText(QtGui.QApplication.translate("MainWindow", "深证成指", None, QtGui.QApplication.UnicodeUTF8)) 50 | self.pushButtonMarketSH.setText(QtGui.QApplication.translate("MainWindow", "上证指数", None, QtGui.QApplication.UnicodeUTF8)) 51 | 52 | def doQueryMarketSH(self): 53 | print("called doQueryMarketSH") 54 | self.doQueryMarket('999999') 55 | 56 | def doQueryMarketSZ(self): 57 | print("called doQueryMarketSH") 58 | self.doQueryMarket('399001') 59 | 60 | def doQueryMarket(self, code): 61 | #日期 市场代码 开盘 最高 最低 收盘 成交量 上涨个股 下跌个股 平盘个股 交易类型I 交易类型II 交易类型III 交易类型IV 风险指标 动能指标 62 | self.ohlc.query(u'select 日期,开盘,最高,最低,收盘,成交量 from Market where 市场代码=? order by 日期 asc'.encode('utf-8'), (code,)) 63 | self.tbl.query(u'select * from Market where 市场代码=? order by 日期 desc'.encode('utf-8'), (code,)) 64 | 65 | #=============================================================================== 66 | # Example 67 | #=============================================================================== 68 | if __name__ == '__main__': 69 | import sys 70 | from PyQt4.QtGui import QMainWindow, QApplication 71 | 72 | class ApplicationWindow(QMainWindow): 73 | def __init__(self): 74 | QMainWindow.__init__(self) 75 | self.widget = MarketWidget(self) 76 | self.widget.setFocus() 77 | self.setCentralWidget(self.widget) 78 | 79 | app = QApplication(sys.argv) 80 | win = ApplicationWindow() 81 | win.show() 82 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /appspot/suas/signedcookie.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python2.5 2 | 3 | """ 4 | Cookie signed with a secret server's key. 5 | 6 | While cookies are a convenient way to store data in clients' browsers, 7 | they can be susceptible to forgery. By attaching a signature to each 8 | (key=value) pair, we can verify that the values the cookies carry are 9 | really those issued by the server. 10 | 11 | This implementation ensure that in a key=value cookie, the value is 12 | really intended for the particular key. 13 | 14 | Each cookie is signed with HMAC-SHA256. 15 | 16 | SignedCookie inherits from SimpleCookie and share the same API. 17 | A SignedCookie is constructed with a secret key as the argument. 18 | 19 | >>> c = SignedCookie('Open, sesame!') 20 | >>> c['user'] = 'username' 21 | >>> c['user'].value 22 | 'username' 23 | >>> c['user'].coded_value 24 | '"usernameCu/vp7hQJ8QsnLoMvFyM6jwyqAyZMIdJcpUZRBE6JYU="' 25 | 26 | Loading also works. 27 | 28 | >>> c.load('Cookie: itemid="12345mwKEC0M1343j/uvi3DIwLsiQWw7UP0Ue/84NxBUwljI="; Max-Age=100') 29 | >>> c['itemid'].value 30 | '12345' 31 | >>> int(c['itemid']['max-age']) 32 | 100 33 | """ 34 | 35 | import re 36 | import hmac 37 | from hashlib import sha256 38 | from base64 import b64encode, b64decode 39 | import Cookie 40 | 41 | 42 | SIG_LEN = 44 # the length of a sha256 hash string is 32, 43 | # thus a base64 encoding of that block 44 | # will always be of length 44 45 | 46 | SIG_PATTERN = re.compile(r'[0-9a-zA-Z+/=]{44}') 47 | # This is the regex pattern for the signature generated by SignedCookie 48 | # which is base64 blob of length 44. 49 | 50 | class BadSignatureError(Exception): 51 | pass 52 | 53 | class SignedCookie(Cookie.SimpleCookie): 54 | def __init__(self, key, input=None): 55 | """ 56 | Initialize the Cookie with a secret key. 57 | """ 58 | self.key = key.encode('ascii') 59 | if input: 60 | self.load(input) 61 | 62 | def __setitem__(self, key, value): 63 | strval = str(value) 64 | sig = b64encode( 65 | hmac.new(self.key + str(key), strval, sha256).digest() 66 | ) 67 | cval = Cookie._quote( strval + sig ) 68 | self._BaseCookie__set(key, strval, cval) 69 | 70 | ## Copied verbatim from Cookie.py 71 | ## This has to be here to be able to use our implementation of __ParseString 72 | def load(self, rawdata): 73 | """Load cookies from a string (presumably HTTP_COOKIE) or 74 | from a dictionary. Loading cookies from a dictionary 'd' 75 | is equivalent to calling: 76 | map(Cookie.__setitem__, d.keys(), d.values()) 77 | """ 78 | if type(rawdata) == type(""): 79 | self.__ParseString(rawdata) 80 | else: 81 | self.update(rawdata) 82 | return 83 | 84 | ## Copied from Cookie.py 85 | ## Changed the last block to use decode & verify signature 86 | def __ParseString(self, str, patt=Cookie._CookiePattern): 87 | i = 0 # Our starting point 88 | n = len(str) # Length of string 89 | M = None # current morsel 90 | 91 | while 0 <= i < n: 92 | # Start looking for a cookie 93 | match = patt.search(str, i) 94 | if not match: break # No more cookies 95 | 96 | K,V = match.group("key"), match.group("val") 97 | i = match.end(0) 98 | 99 | # Parse the key, value in case it's metainfo 100 | if K[0] == "$": 101 | # We ignore attributes which pertain to the cookie 102 | # mechanism as a whole. See RFC 2109. 103 | # (Does anyone care?) 104 | if M: 105 | M[ K[1:] ] = V 106 | elif K.lower() in Cookie.Morsel._reserved: 107 | if M: 108 | M[ K ] = Cookie._unquote(V) 109 | else: 110 | if not SIG_PATTERN.search(V): 111 | pass 112 | uval = Cookie._unquote(V) 113 | real_val = uval[:-SIG_LEN] 114 | try: 115 | sig = b64decode( uval[-SIG_LEN:] ) 116 | except TypeError: 117 | # Incorrect padding 118 | raise BadSignatureError("Bad signature for cookie '%s'" % K) 119 | # TODO: use constant time string comparison 120 | if sig != hmac.new(self.key + K, real_val, sha256).digest(): 121 | raise BadSignatureError("Bad signature for cookie '%s'" % K) 122 | self._BaseCookie__set(K, real_val, V) 123 | M = self[K] 124 | -------------------------------------------------------------------------------- /DEONE/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import httplib2 4 | #import urllib2 5 | #import httplib 6 | from auth import generate_auth, generate_timestamp 7 | import random, time 8 | import simplejson 9 | 10 | class App3Client(object): 11 | """ 12 | Simple REST Client for App3. 13 | """ 14 | def __init__(self, host, user_name='test', user_pass='test'): 15 | self.__host = host 16 | self.__user_name = user_name 17 | self.__user_pass = user_pass 18 | self.h = httplib2.Http() 19 | 20 | def post(self, resource, params=None): 21 | """ 22 | Executes a POST request on the specified resource. 23 | """ 24 | resp, body = self.__request( 25 | method = 'POST', 26 | url = "/rest/%s/" % (resource), 27 | params = params, 28 | ) 29 | #print(resp, body) 30 | if (resp is not None) and (resp['status'] == '201'): 31 | return body 32 | else: 33 | return None 34 | 35 | def list(self, resource, params=None): 36 | """ 37 | Lists the all of the resources of type resource. 38 | """ 39 | resp, body = self.__request( 40 | method = 'GET', 41 | url = "/rest/%s/" % resource, 42 | params = params, 43 | ) 44 | 45 | if (resp is not None) and (resp['status'] == '200'): 46 | return body 47 | else: 48 | return None 49 | 50 | def get(self, resource, id, params=None): 51 | """ 52 | Retrieves the resource specified. 53 | """ 54 | resp, body = self.__request( 55 | method = 'GET', 56 | url = "/rest/%s/%s/" % (resource, id), 57 | params = params, 58 | ) 59 | 60 | if (resp is not None) and (resp['status'] == '200'): 61 | return body 62 | else: 63 | return None 64 | 65 | def exists(self, resource, id): 66 | """ 67 | Returns whether the resource specified exists in the datastore. 68 | """ 69 | resp = self.__request( 70 | method = 'GET', 71 | url = "/rest/%s/%s/" % (resource, id), 72 | ) 73 | return (resp is not None) and (resp['status'] == '200') 74 | 75 | def delete(self, resource, id): 76 | """ 77 | Deletes the specified resource from the datastore. 78 | """ 79 | resp, body = self.__request( 80 | method = 'DELETE', 81 | url = "/rest/%s/%s/" % (resource, id), 82 | ) 83 | #print(resp, body) 84 | if (resp is not None) and (resp['status'] == '200'): 85 | return body 86 | else: 87 | return None 88 | 89 | def test(self): 90 | resp, body = self.__request( 91 | method = 'GET', 92 | url = "/", 93 | ) 94 | 95 | if (resp is not None) and (resp['status'] == '200'): 96 | return body 97 | else: 98 | return None 99 | 100 | def __request(self, method, url, params=None): 101 | """ 102 | Internal method for executing all of the REST requests. 103 | """ 104 | if params is not None: 105 | params = simplejson.dumps(params) 106 | 107 | self.app3_timestamp = generate_timestamp() 108 | 109 | headers = { 110 | #"Content-type": "application/json", 111 | 'App3-User': self.__user_name, 112 | 'App3-Pass': self.__user_pass, 113 | 'App3-Timestamp': self.app3_timestamp, 114 | 'App3-Auth': generate_auth(self.__user_name, self.app3_timestamp), 115 | } 116 | 117 | #h = httplib2.Http(".cache") 118 | #h.add_credentials('name', 'password') 119 | resp, body = self.h.request("http://"+self.__host + url, method, body=params, headers=headers ) 120 | #print(resp, body) 121 | return resp, body 122 | 123 | #=============================================================================== 124 | # Example 125 | #=============================================================================== 126 | if __name__ == '__main__': 127 | from client import App3Client 128 | c = App3Client('de-one.appspot.com', 'test', 'test') 129 | print(c.test()) 130 | print(c.list('Deone')) 131 | -------------------------------------------------------------------------------- /appspot/restlet/jsonutil.py: -------------------------------------------------------------------------------- 1 | """ Utilities for JSON REST CRUD support for GAE db models. 2 | 3 | Terminology: a subclass of db.Model is known as "a Model"; an instance of 4 | such a subclass is known as "an entity". 5 | 6 | Data is said to be in JSONed or JSONable form if it contains only dicts, lists 7 | and scalars (strings, numbers) in a form that is correctly serializable into a 8 | JSON-format string. 9 | 10 | In particular, a "jobj" is a JSONed dict with a key 'id' mapping the string 11 | format of the numeric value of an entity; each other key must be the name of 12 | a property of that entity's Model, and the corresponding value must be a string 13 | that can be deserialized into a value of that property's type. 14 | """ 15 | import re 16 | 17 | import restutil 18 | from django.utils import simplejson 19 | 20 | 21 | def id_of(entity): 22 | """ Make a {'id': } dict for an entity. 23 | 24 | Args: 25 | entity: an entity 26 | Returns: 27 | a jobj corresponding to the entity 28 | """ 29 | return dict(id=restutil.id_of(entity)) 30 | 31 | 32 | # RE to match: optional /, classname, optional /, ID of 0+ numeric digits 33 | CLASSNAME_ID_RE = re.compile(r'^/?(\w+)/?(\d*)$') 34 | 35 | def path_to_classname_and_id(path): 36 | """ Get a (classname, id) pair from a path. 37 | 38 | Args: 39 | path: a path string to anaylyze 40 | Returns: 41 | a 2-item tuple: 42 | (None, '') if the path does not match CLASSNAME_ID_RE 43 | (classname, idstring) if the path does match 44 | [idstring may be '', or else a string of digits] 45 | """ 46 | mo = CLASSNAME_ID_RE.match(path) 47 | if mo: return mo.groups() 48 | else: return (None, '') 49 | 50 | 51 | def send_json(response_obj, jdata): 52 | """ Send data in JSON form to an HTTP-response object. 53 | 54 | Args: 55 | response_obj: an HTTP response object 56 | jdata: a dict or list in correct 'JSONable' form 57 | Side effects: 58 | sends the JSON form of jdata on response.out 59 | """ 60 | response_obj.content_type = 'application/json' 61 | simplejson.dump(jdata, response_obj.out) 62 | 63 | 64 | def receive_json(request_obj): 65 | """ Receive data in JSON form from an HTTP-request object. 66 | 67 | Args: 68 | request_obj: an HTTP request object (with body in JSONed form) 69 | Returns: 70 | the JSONable-form result of loading the request's body 71 | """ 72 | return simplejson.loads(request_obj.body) 73 | 74 | 75 | def make_jobj(entity): 76 | """ Make a JSONable dict (a jobj) given an entity. 77 | 78 | Args: 79 | entity: an entity 80 | Returns: 81 | the JSONable-form dict (jobj) for the entity 82 | """ 83 | model = type(entity) 84 | jobj = id_of(entity) 85 | props = restutil.allProperties(model) 86 | for property_name, property_value in props: 87 | value_in_entity = getattr(entity, property_name, None) 88 | if value_in_entity is not None: 89 | to_string = getattr(model, property_name + '_to_string') 90 | jobj[property_name] = to_string(value_in_entity) 91 | return jobj 92 | 93 | 94 | def parse_jobj(model, jobj): 95 | """ Make dict suitable for instantiating model, given a jobj. 96 | 97 | Args: 98 | model: a Model 99 | jobj: a jobj 100 | Returns: 101 | a dict d such that calling model(**d) properly makes an entity 102 | """ 103 | result = dict() 104 | for property_name, property_value in jobj.iteritems(): 105 | # ensure we have an ASCII string, not a Unicode one 106 | property_name = str(property_name) 107 | from_string = getattr(model, property_name + '_from_string') 108 | property_value = from_string(property_value) 109 | if property_value is not None: 110 | result[property_name] = property_value 111 | return result 112 | 113 | 114 | def make_entity(model, jobj): 115 | """ Makes an entity whose type is model with the state given by jobj. 116 | 117 | Args: 118 | model: a Model 119 | jobj: a jobj 120 | Side effects: 121 | creates and puts an entity of type model, w/state per jobj 122 | Returns: 123 | a jobj representing the newly created entity 124 | """ 125 | entity_dict = parse_jobj(model, jobj) 126 | entity = model(**entity_dict) 127 | entity.put() 128 | jobj = make_jobj(entity) 129 | jobj.update(id_of(entity)) 130 | return jobj 131 | 132 | 133 | def update_entity(entity, jobj): 134 | """ Updates an entity's state as per properties given in jobj. 135 | 136 | Args: 137 | entity: an entity 138 | jobj: a jobj 139 | Side effects: 140 | updates the entity with properties as given by jobj 141 | Returns: 142 | a jobj representing the whole new state of the entity 143 | """ 144 | new_entity_data = parse_jobj(type(entity), jobj) 145 | for property_name, property_value in new_entity_data.iteritems(): 146 | setattr(entity, property_name, property_value) 147 | entity.put() 148 | return make_jobj(entity) 149 | -------------------------------------------------------------------------------- /DEONE/trading.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4 import QtCore, QtGui 4 | 5 | from ohlc import OHLCWidget 6 | from querytable import QueryTableWidget 7 | 8 | class TradingWidget(QtGui.QWidget): 9 | def __init__(self, parent): 10 | super(QtGui.QWidget, self).__init__(parent) 11 | 12 | self.setObjectName("tabTrading") 13 | mainLayout = QtGui.QHBoxLayout() 14 | 15 | vLayoutOHLC = QtGui.QVBoxLayout() 16 | self.ohlc = OHLCWidget(self) 17 | self.ohlc.setObjectName("OHLCWidget") 18 | vLayoutOHLC.addWidget(self.ohlc) 19 | 20 | self.tblWidgetMarket = QueryTableWidget(self) 21 | self.tblWidgetMarket.setObjectName("tblWidgetMarket") 22 | vLayoutOHLC.addWidget(self.tblWidgetMarket) 23 | 24 | mainLayout.addLayout(vLayoutOHLC) 25 | 26 | vLayoutInfo = QtGui.QVBoxLayout() 27 | 28 | self.label_Title = QtGui.QLabel(self) 29 | vLayoutInfo.addWidget(self.label_Title) 30 | self.label_FX = QtGui.QLabel(self) 31 | vLayoutInfo.addWidget(self.label_FX) 32 | self.tblWidgetFX = QueryTableWidget(self) 33 | self.tblWidgetFX.setObjectName("tblWidgetFX") 34 | vLayoutInfo.addWidget(self.tblWidgetFX) 35 | 36 | self.label_JY = QtGui.QLabel(self) 37 | vLayoutInfo.addWidget(self.label_JY) 38 | self.tblWidgetJY = QueryTableWidget(self) 39 | self.tblWidgetJY.setObjectName("tblWidgetJY") 40 | vLayoutInfo.addWidget(self.tblWidgetJY) 41 | 42 | self.label_XX = QtGui.QLabel(self) 43 | vLayoutInfo.addWidget(self.label_XX) 44 | self.tblWidgetXX = QueryTableWidget(self) 45 | self.tblWidgetXX.setObjectName("tblWidgetXX") 46 | vLayoutInfo.addWidget(self.tblWidgetXX) 47 | 48 | self.label_ZS = QtGui.QLabel(self) 49 | vLayoutInfo.addWidget(self.label_ZS) 50 | self.tblWidgetZS = QueryTableWidget(self) 51 | self.tblWidgetZS.setObjectName("tblWidgetZS") 52 | vLayoutInfo.addWidget(self.tblWidgetZS) 53 | 54 | mainLayout.addLayout(vLayoutInfo) 55 | self.setLayout(mainLayout) 56 | 57 | self.retranslateUi() 58 | 59 | QtCore.QObject.connect(self.tblWidgetXX, QtCore.SIGNAL("selectionChanged()"), self.tblWidgetXX_selectionChanged) 60 | self.init() 61 | 62 | def retranslateUi(self): 63 | self.label_Title.setText(QtGui.QApplication.translate("MainWindow", "DeOne 量化策略交易模型", None, QtGui.QApplication.UnicodeUTF8)) 64 | self.label_FX.setText(QtGui.QApplication.translate("MainWindow", "市场风险分析", None, QtGui.QApplication.UnicodeUTF8)) 65 | self.label_JY.setText(QtGui.QApplication.translate("MainWindow", "交易数据", None, QtGui.QApplication.UnicodeUTF8)) 66 | self.label_XX.setText(QtGui.QApplication.translate("MainWindow", "详细消息", None, QtGui.QApplication.UnicodeUTF8)) 67 | self.label_ZS.setText(QtGui.QApplication.translate("MainWindow", "操作指示", None, QtGui.QApplication.UnicodeUTF8)) 68 | 69 | def tblWidgetXX_selectionChanged(self): 70 | print("call tblWidgetXX_selectionChanged") 71 | row=self.tblWidgetXX.currentRow() 72 | code = str(self.tblWidgetXX.item(row,0).text()) 73 | print(code) 74 | #日期 股票代码 公司名称 开盘价格 最高价格 最低价格 收盘价格 成交量 75 | self.ohlc.query(u'select 日期,开盘价格,最高价格,最低价格,收盘价格,成交量 from StockHist where 股票代码=? order by 日期 asc'.encode('utf-8'), (code,)) 76 | self.tblWidgetZS.query(u'select 日期,初始买入,最低买入,建仓价位,目标价位,最高目标,止损价位 from Suggestion where 股票代码=? and 日期=?'.encode('utf-8'), (code, self.date,)) 77 | 78 | def init(self): 79 | self.date = '2009-07-10' 80 | #日期 市场代码 开盘 最高 最低 收盘 成交量 上涨个股 下跌个股 平盘个股 交易类型I 交易类型II 交易类型III 交易类型IV 风险指标 动能指标 81 | self.tblWidgetMarket.query(u'select 日期,市场代码,开盘,最高,最低,收盘,成交量,上涨个股,下跌个股,平盘个股 from Market where 日期=?'.encode('utf-8'), (self.date,)) 82 | self.tblWidgetFX.query(u'select 日期,市场代码,风险指标,动能指标 from Market where 日期=?'.encode('utf-8'), (self.date,)) 83 | self.tblWidgetJY.query(u'select 日期,市场代码,交易类型I,交易类型II,交易类型III,交易类型IV from Market where 日期=?'.encode('utf-8'), (self.date,)) 84 | #日期 股票代码 公司名称 交易类型 初始买入 最低买入 建仓价位 目标价位 最高目标 止损价位 85 | self.tblWidgetXX.query(u'select 股票代码,公司名称,交易类型 from Suggestion where 日期=?'.encode('utf-8'), (self.date,)) 86 | 87 | #=============================================================================== 88 | # Example 89 | #=============================================================================== 90 | if __name__ == '__main__': 91 | import sys 92 | from PyQt4.QtGui import QMainWindow, QApplication 93 | 94 | class ApplicationWindow(QMainWindow): 95 | def __init__(self): 96 | QMainWindow.__init__(self) 97 | self.widget = TradingWidget(self) 98 | self.widget.setFocus() 99 | self.setCentralWidget(self.widget) 100 | 101 | app = QApplication(sys.argv) 102 | win = ApplicationWindow() 103 | win.show() 104 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /DEONE/querytable.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4 import QtCore, QtGui, Qt 4 | from matplotlibwidget import MatplotlibWidget 5 | 6 | import sqlite3 7 | import datetime 8 | 9 | class QueryTableWidget(QtGui.QTableWidget): 10 | def __init__(self, parent): 11 | super(QtGui.QTableWidget, self).__init__(parent) 12 | self.setObjectName("DBTableWidget") 13 | 14 | self.horizontalHeader().setStretchLastSection(True) 15 | self.setSortingEnabled(True) 16 | self.sortByColumn(0, QtCore.Qt.AscendingOrder) 17 | 18 | self.setShowGrid(True) 19 | self.verticalHeader().hide() 20 | self.verticalHeader().setDefaultSectionSize(20) 21 | self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) 22 | self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) 23 | self.setAlternatingRowColors(True) 24 | self.verticalHeader().setResizeMode(Qt.QHeaderView.Fixed) 25 | #self.setHorizontalScrollBarPolicy(Qt.Qt.ScrollBarAlwaysOff) 26 | self.setMouseTracking(True) 27 | #self.setEnabled(False) 28 | 29 | def keyPressEvent(self, event) : 30 | print("keyPressEvent") 31 | #print(event.modifiers(), event.key()) 32 | if event.modifiers() == Qt.Qt.AltModifier : 33 | if event.key() == Qt.Qt.Key_Up : 34 | print("alt+up") 35 | elif event.key() == Qt.Qt.Key_Down : 36 | print("alt+down") 37 | else : 38 | Qt.QTableWidget.keyPressEvent(self, event) 39 | 40 | def mousePressEvent(self, event): 41 | print("mousePressEvent") 42 | if event.button() == Qt.Qt.LeftButton and self.indexAt(event.pos()).row() > -1 : 43 | print(event.button, self.indexAt(event.pos()).row()) 44 | Qt.QTableWidget.mousePressEvent(self, event) 45 | 46 | def selectionChanged(self, new, old): 47 | print("selectionChanged", new, old) 48 | #row=self.currentRow() 49 | #col=self.currentColumn() 50 | #print(self.item(row,0).text()) 51 | #print(self.item(row,2).text()) 52 | self.emit(QtCore.SIGNAL("selectionChanged()")) 53 | Qt.QTableWidget.selectionChanged(self, new, old) 54 | 55 | def query(self, sql, parameters=None): 56 | self.clear() 57 | 58 | # select data from sqlite3 db 59 | deone_db = r'data/DEONE.db' 60 | con = sqlite3.connect(deone_db) 61 | con.text_factory=str 62 | con.row_factory = sqlite3.Row 63 | cur = con.cursor() 64 | #t = u'深发展A'.encode('utf-8') 65 | #cur.execute(u'select * from StockHist where 股票名称=?'.encode('utf-8'), (t,)) 66 | #cur.execute('select * from StockHist') 67 | cur.execute(sql, parameters) 68 | 69 | irow = 0 70 | ncols = 0 71 | for irow in xrange(100): 72 | row = cur.fetchone() 73 | if row == None: 74 | break 75 | #print(row) 76 | ncols = len(row) 77 | if irow == 0: 78 | self.setColumnCount(ncols) 79 | #self.setStretchLastSection(True) 80 | 81 | for ind in xrange(ncols): 82 | s = row.keys()[ind].decode('utf-8') 83 | item = QtGui.QTableWidgetItem(s) 84 | item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) 85 | self.setHorizontalHeaderItem(ind, item) 86 | self.insertRow(irow) 87 | for icol in xrange(ncols): 88 | s = row[icol].decode('utf-8') 89 | item = QtGui.QTableWidgetItem(s) 90 | #item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) 91 | item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) 92 | item.setWhatsThis("You can change this task's comment, start time and end time."); 93 | self.setItem(irow,icol,item); 94 | con.close() 95 | self.setRowCount(irow) 96 | 97 | # Sorting columns 98 | for col in range(ncols): 99 | self.resizeColumnToContents(col) 100 | self.setCurrentItem(self.item(0,0)) 101 | 102 | #=============================================================================== 103 | # Example 104 | #=============================================================================== 105 | if __name__ == '__main__': 106 | import sys 107 | from PyQt4.QtGui import QMainWindow, QApplication 108 | 109 | class ApplicationWindow(QMainWindow): 110 | def __init__(self): 111 | QMainWindow.__init__(self) 112 | self.tblwidget = QueryTableWidget(self) 113 | self.tblwidget.setFocus() 114 | self.setCentralWidget(self.tblwidget) 115 | 116 | app = QApplication(sys.argv) 117 | win = ApplicationWindow() 118 | win.show() 119 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /DEONE/sector.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4 import QtCore, QtGui 4 | 5 | from querytable import QueryTableWidget 6 | 7 | class SectorWidget(QtGui.QWidget): 8 | def __init__(self, parent): 9 | super(QtGui.QWidget, self).__init__(parent) 10 | 11 | self.setObjectName("tabSector") 12 | 13 | mainLayout = QtGui.QHBoxLayout() 14 | 15 | vLayoutLeft = QtGui.QVBoxLayout() 16 | self.label_DX = QtGui.QLabel(self) 17 | vLayoutLeft.addWidget(self.label_DX) 18 | 19 | hLayoutQuery = QtGui.QHBoxLayout() 20 | self.label_RQ = QtGui.QLabel(self) 21 | hLayoutQuery.addWidget(self.label_RQ) 22 | 23 | self.lineEdit = QtGui.QLineEdit(self) 24 | self.lineEdit.setGeometry(QtCore.QRect(80, 0, 113, 20)) 25 | self.lineEdit.setObjectName("lineEdit") 26 | hLayoutQuery.addWidget(self.lineEdit) 27 | 28 | self.pushButtonQuery = QtGui.QPushButton(self) 29 | self.pushButtonQuery.setGeometry(QtCore.QRect(210, 0, 75, 23)) 30 | self.pushButtonQuery.setObjectName("pushButtonQuery") 31 | hLayoutQuery.addWidget(self.pushButtonQuery) 32 | 33 | vLayoutLeft.addLayout(hLayoutQuery) 34 | 35 | self.tblWidgetBK = QueryTableWidget(self) 36 | self.tblWidgetBK.setObjectName("tblWidgetBK") 37 | vLayoutLeft.addWidget(self.tblWidgetBK) 38 | 39 | mainLayout.addLayout(vLayoutLeft) 40 | 41 | vLayoutRight = QtGui.QVBoxLayout() 42 | self.label_GG = QtGui.QLabel(self) 43 | vLayoutRight.addWidget(self.label_GG) 44 | self.tblWidgetGG = QueryTableWidget(self) 45 | self.tblWidgetGG.setObjectName("tblWidgetGG") 46 | vLayoutRight.addWidget(self.tblWidgetGG) 47 | 48 | self.label_ZS = QtGui.QLabel(self) 49 | vLayoutRight.addWidget(self.label_ZS) 50 | self.tblWidgetZS = QueryTableWidget(self) 51 | self.tblWidgetZS.setObjectName("tblWidgetZS") 52 | vLayoutRight.addWidget(self.tblWidgetZS) 53 | 54 | mainLayout.addLayout(vLayoutRight) 55 | 56 | self.setLayout(mainLayout) 57 | 58 | self.retranslateUi() 59 | 60 | QtCore.QObject.connect(self.pushButtonQuery, QtCore.SIGNAL("pressed()"), self.doQuery) 61 | #self.emit(QtCore.SIGNAL("selectionChanged()")) 62 | QtCore.QObject.connect(self.tblWidgetBK, QtCore.SIGNAL("selectionChanged()"), self.tblWidgetBK_selectionChanged) 63 | QtCore.QObject.connect(self.tblWidgetGG, QtCore.SIGNAL("selectionChanged()"), self.tblWidgetGG_selectionChanged) 64 | 65 | 66 | self.lineEdit.setText('2009-07-10') 67 | self.doQuery() 68 | 69 | def retranslateUi(self): 70 | self.label_DX.setText(QtGui.QApplication.translate("MainWindow", "板块动向", None, QtGui.QApplication.UnicodeUTF8)) 71 | self.label_RQ.setText(QtGui.QApplication.translate("MainWindow", "交易日期", None, QtGui.QApplication.UnicodeUTF8)) 72 | self.pushButtonQuery.setText(QtGui.QApplication.translate("MainWindow", "查询", None, QtGui.QApplication.UnicodeUTF8)) 73 | self.label_GG.setText(QtGui.QApplication.translate("MainWindow", "板块个股", None, QtGui.QApplication.UnicodeUTF8)) 74 | self.label_ZS.setText(QtGui.QApplication.translate("MainWindow", "操作指示", None, QtGui.QApplication.UnicodeUTF8)) 75 | 76 | def doQuery(self): 77 | print("called doQuery") 78 | date = str(self.lineEdit.text()) 79 | #self.tblWidgetBK.query(u'select * from CategoryHist where 股票代码=?'.encode('utf-8'), (date,)) 80 | self.tblWidgetBK.query(u'select * from CategoryHist'.encode('utf-8'), ()) 81 | pass 82 | 83 | def tblWidgetBK_selectionChanged(self): 84 | print("called tblWidgetBK_selectionChanged") 85 | row=self.tblWidgetBK.currentRow() 86 | code = str(self.tblWidgetBK.item(row,0).text()) 87 | print(code) 88 | #select * from StockHist where 股票代码 in (select 股票代码 from StockCategory where 板块代码='YH') 89 | self.tblWidgetGG.query(u'select * from StockHist where 股票代码 in (select 股票代码 from StockCategory where 板块代码=?) order by 日期 desc'.encode('utf-8'), (code,)) 90 | 91 | def tblWidgetGG_selectionChanged(self): 92 | print("called tblWidgetGG_selectionChanged") 93 | row=self.tblWidgetGG.currentRow() 94 | code = str(self.tblWidgetGG.item(row,2).text()) 95 | print(code) 96 | self.tblWidgetZS.query(u'select * from Suggestion where 股票代码=? order by 日期 desc'.encode('utf-8'), (code,)) 97 | 98 | #=============================================================================== 99 | # Example 100 | #=============================================================================== 101 | if __name__ == '__main__': 102 | import sys 103 | from PyQt4.QtGui import QMainWindow, QApplication 104 | 105 | class ApplicationWindow(QMainWindow): 106 | def __init__(self): 107 | QMainWindow.__init__(self) 108 | self.widget = SectorWidget(self) 109 | self.widget.setFocus() 110 | self.setCentralWidget(self.widget) 111 | 112 | app = QApplication(sys.argv) 113 | win = ApplicationWindow() 114 | win.show() 115 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /appspot/suas/auth_handlers.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python2.5 2 | #----------------------- 3 | 4 | """ 5 | Provide user authentication request handlers. Their names should be 6 | self-explanatory. You might need to adapt to suit your app's need. 7 | 8 | Utility method: login_required -- for use as a decorator. 9 | """ 10 | 11 | from google.appengine.ext.webapp import template 12 | 13 | from session import RequestHandler 14 | from users import User, UserSignup, salt_n_hash, signup_id 15 | 16 | import logging 17 | 18 | # Adapt to use your app's view. 19 | SIGNUP_VIEW = template.Template(""" 20 | Sign up 21 | 22 | {% if session.flash_msg %} 23 |

{{ session.flash_msg }}

24 | {% endif %} 25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 |

Login

33 | 34 | """) 35 | 36 | LOGIN_VIEW = template.Template(""" 37 | Login 38 | 39 | {% if session.flash_msg %} 40 |

{{ session.flash_msg }}

41 | {% endif %} 42 |
43 |
44 |
45 | 46 |
47 |

Sign up

48 | 49 | """) 50 | 51 | class Signup(RequestHandler): 52 | def get(self): 53 | ctx = template.Context({"session": self.session}) 54 | self.response.out.write(SIGNUP_VIEW.render(ctx)) 55 | 56 | def post(self): 57 | nickname = self.request.get('nickname') 58 | email = self.request.get('email') 59 | password = self.request.get('password') 60 | password2 = self.request.get('password2') 61 | if password2 != password: 62 | self.session.start(None) 63 | self.session[ 'flash_msg' ] = '

Password fields did not match.

' 64 | self.redirect('/signup') 65 | return 66 | user = User.get_or_insert(nickname=nickname, 67 | email=email, 68 | pwd=salt_n_hash(password), 69 | key_name=nickname) 70 | self.session.start(None) 71 | if not User.authenticate(nickname, password): 72 | self.session[ 'flash_msg' ] = '

Sorry, the nickname you chose is already taken.

' 73 | self.redirect(self.request.url) 74 | return 75 | id = signup_id(nickname) 76 | signup = UserSignup(user=user, key_name=id) 77 | signup.put() 78 | confirm_url = self.request.relative_url('confirmsignup?id='+id) 79 | logging.info("user:" + nickname + " password:" + password + " confirm_url:" +confirm_url) 80 | from google.appengine.api import mail 81 | #sender = 'Registrar ' 82 | sender = 'deone.appspot@gmail.com' 83 | subject = 'Confirm your registration' 84 | body = \ 85 | 'Hello %s,\n\n' % nickname + \ 86 | 'To confirm your registration, please visit the link below:\n\n' + \ 87 | '<%s>\n' % confirm_url 88 | mail.send_mail( sender, email, subject, body ) 89 | self.session['flash_msg'] = \ 90 | '

Thank you for signing up, %s! A confirmation ' % nickname + \ 91 | 'message is on its way to your email inbox. It will contain a link ' + \ 92 | 'which you will need to visit in order to complete your registration.

' + \ 93 | '

See you soon!

' 94 | self.redirect('/') 95 | 96 | 97 | class ConfirmSignup(RequestHandler): 98 | def get(self): 99 | id = self.request.get('id') 100 | signup = UserSignup.get_by_key_name(id) 101 | if not signup: 102 | self.error(401) 103 | return 104 | user = signup.user 105 | user.suspended = False 106 | user.put() 107 | signup.delete() 108 | self.session.start(user) 109 | self.session['flash_msg'] = '

Your account has been confirmed.

' 110 | #self.redirect('/user/' + user.nickname) 111 | self.redirect('/') 112 | 113 | 114 | class Login(RequestHandler): 115 | """Handle /login?redirect=%s request. You will need to define your GET method handler.""" 116 | def get(self): 117 | ctx = template.Context({"session": self.session}) 118 | self.response.out.write(LOGIN_VIEW.render(ctx)) 119 | 120 | def post(self): 121 | user = User.authenticate(self.request.get('nickname'), self.request.get('password')) 122 | if user and not user.suspended: 123 | self.session.start(user) 124 | self.session['flash_msg'] = '

Welcome, %s!

' % user.nickname 125 | #redirect = self.request.get('redirect') 126 | #self.redirect(redirect) 127 | self.redirect('/') 128 | else: 129 | self.session.start( None ) 130 | self.session['flash_msg'] = '

Incorrect nickname/password combination. Sorry!

' 131 | self.redirect(self.request.url) 132 | 133 | 134 | def login_required(handler_method): 135 | """ 136 | A decorator to require that a user be logged in to access a handler method. 137 | 138 | >>> @login_required 139 | ... def get(self): 140 | ... self.response.out.write('Hello, ' + self.session.user.nickname) 141 | 142 | We will redirect to a login page if the user is not logged in. 143 | """ 144 | def check_login(self, *args): 145 | if not self.session.user: 146 | self.redirect('/login?' + 'redirect=' + self.request.url) 147 | else: 148 | handler_method(self, *args) 149 | return check_login 150 | 151 | 152 | class Logout(RequestHandler): 153 | def get(self): 154 | if not self.session.user: 155 | self.error(404) 156 | return 157 | nickname = self.session.user.nickname 158 | self.session.start(None) 159 | self.session['flash_msg'] = '

Goodbye, %s!

' % nickname 160 | self.redirect('/login') 161 | 162 | # Add this to your app's routes. 163 | ROUTES = [ 164 | ('/signup', Signup), 165 | ('/confirmsignup', ConfirmSignup), 166 | ('/login', Login), 167 | ('/logout', Logout) 168 | ] 169 | 170 | -------------------------------------------------------------------------------- /appspot/suas/session.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python2.5 2 | #------------------- 3 | 4 | """ 5 | Session based solely on signed cookie (no Datastore/memcache) 6 | 7 | A CookieSession persists between HTTP requests & responses by using 8 | signed cookies referring to a particular session ID. All session-related 9 | cookies are signed using the SID and the site's secret key, ensuring that: 10 | * In a key=value cookie, the value is really intended for the 11 | particular key. 12 | * Cookies are really pertinent to a particular SID that signed it. 13 | * Cookies were really issued by the server for a particular session. 14 | 15 | The RequestHandler class extends webapp.RequestHandler to provide 16 | access to the current session as an instance variable. 17 | 18 | Limitations: 19 | * currently unsigned cookies are simply ignored 20 | """ 21 | 22 | from time import time, gmtime 23 | from calendar import timegm 24 | from hashlib import md5 25 | 26 | from google.appengine.ext import webapp 27 | 28 | from signedcookie import SignedCookie, BadSignatureError, SIG_LEN 29 | from users import User 30 | 31 | SECRET_KEY = 'Open, sesame!' 32 | # A secret key unique to your application. 33 | 34 | SESSION_TTL = 604800 # 604800s = 7d 35 | # the life time of a "persistent" session authenticator, in seconds. 36 | # As long as the user comes back within that time frame, we will nenew 37 | # his session for a full period. In case he doesn't, he will have to 38 | # login again. 39 | 40 | SID_TTL = 900 # 900s = 15m 41 | # the minimum interval between requests after which 42 | # we expire the old and issue a new ID. 43 | 44 | 45 | class NoSIDError(Exception): 46 | pass 47 | 48 | class CookieSession: 49 | """ 50 | Provides dictionary-like storage/access to signed cookies. 51 | The keys 'SID', 'user', and 'atime' are internally used by the 52 | session object and the request handler, do not change them. 53 | 54 | Attributes: 55 | user the current user 56 | 57 | flash_msg a possible flash message set by the previous page, 58 | automatically popped from the 'flash_msg' cookie 59 | 60 | cookies the underlying cookies 61 | 62 | Class method: 63 | load(request, response) returns a session instance loaded from cookies 64 | 65 | Instance methods: 66 | start(user, persist=False) 67 | end() 68 | 69 | Do not set/del the same key multiple times, as each __setitem__ 70 | call adds a 'Set-Cookie' header in the response. This is due 71 | to webapp's inability to deals directly with cookies or delete 72 | multi-valued headers. You can modify the cookies attribute 73 | directly which do not modify the response, then call set_cookie 74 | to add a header manually. 75 | 76 | Also, since cookie values are strings, you will need to do 77 | serialization/deserialization yourself, if necessary. 78 | """ 79 | def __init__(self, user, response): 80 | self.user = user 81 | self.response = response 82 | self.persist = False 83 | id = self.__gen_id() 84 | self.cookies = SignedCookie(SECRET_KEY + id) 85 | self.cookies['SID'] = id 86 | self.cookies['atime'] = repr( timegm(gmtime()) ) 87 | 88 | def __gen_id(self): 89 | # This is predictable, but that's ok. 90 | # We just need it to be unique, *especially* for different users 91 | if self.user is None: 92 | name = 'Anonymous' 93 | else: 94 | name = self.user.nickname 95 | return md5( name + repr(time()) ).hexdigest() 96 | 97 | def __getitem__(self, key): 98 | return self.cookies[key].value 99 | 100 | def get(self, key, default=None): 101 | v = self.cookies.get(key) 102 | if v is not None: 103 | return v.value 104 | else: 105 | return default 106 | 107 | def pop(self, key, default=None): 108 | try: 109 | v = self[key] 110 | except KeyError: 111 | if default is None: 112 | raise 113 | else: 114 | return default 115 | else: 116 | self.expire_cookie(key) 117 | return v 118 | 119 | def __setitem__(self, key, value): 120 | self.cookies[key] = value 121 | if self.persist: 122 | self.cookies[key]['max-age'] = SESSION_TTL 123 | self.set_cookie( key ) 124 | 125 | def set_cookie(self, key): 126 | self.response.headers.add_header( 127 | 'Set-Cookie', self.cookies[key].output()[12:] 128 | ) 129 | 130 | def __delitem__(self, key): 131 | self.expire_cookie(key) 132 | 133 | def expire_cookie(self, key): 134 | self.cookies[key]['max-age'] = 0 135 | self.set_cookie( key ) 136 | 137 | @classmethod 138 | def load(klass, request, response): 139 | """ 140 | Load the session cookies from the request, 141 | returning a new instance with the response. 142 | """ 143 | try: 144 | id = request.cookies['SID'][1:-SIG_LEN-1] 145 | except KeyError: 146 | raise NoSIDError 147 | else: 148 | c = SignedCookie(SECRET_KEY + id) 149 | c.load(request.environ['HTTP_COOKIE']) 150 | try: 151 | user = User.get_by_key_name(c['user'].value) 152 | except KeyError: 153 | user = None 154 | session = klass(user, response) 155 | session.cookies = c 156 | return session 157 | 158 | def start(self, user, persist=False): 159 | """ 160 | Log the user in, where user is an object with a nickname 161 | attribute. 162 | 163 | If persist=False, the browser will by default log the 164 | user out when it closes; otherwise we will keep him logged 165 | in as long as he comes back within SESSION_TTL. 166 | """ 167 | self.user = user 168 | self.persist = persist 169 | if user is None: 170 | self.set_cookie( 'SID' ) 171 | self.set_cookie( 'atime' ) 172 | if self.cookies.has_key ( 'user' ): 173 | self.expire_cookie ( 'user' ) 174 | else: 175 | self.cookies['user'] = user.nickname 176 | self.regen() 177 | ## TODO: Cache-control 178 | 179 | def regen(self): 180 | """Regenerate a new SID""" 181 | id = self.__gen_id() 182 | c = SignedCookie(SECRET_KEY + id) 183 | for key, morsel in self.cookies.items(): 184 | c[key] = morsel.value 185 | c[key].update( morsel.items() ) ## preserves Max-Age 186 | c['SID'] = id 187 | c['atime'] = repr( timegm(gmtime()) ) 188 | self.cookies = c 189 | for k in self.cookies.keys(): 190 | self.set_cookie( k ) 191 | 192 | def end(self): 193 | """Expire all cookies""" 194 | self.user = None 195 | for key in self.cookies.keys(): 196 | self.expire_cookie( key ) 197 | 198 | 199 | class RequestHandler(webapp.RequestHandler): 200 | """ 201 | A session-capable request handler. 202 | 203 | Attribute: 204 | session 205 | """ 206 | def initialize(self, request, response): 207 | super(RequestHandler, self).initialize(request, response) 208 | try: 209 | self.session = CookieSession.load(request, response) 210 | except NoSIDError: 211 | self.session = CookieSession(None, self.response) 212 | except BadSignatureError: 213 | self.session = CookieSession(None, self.response) 214 | else: 215 | self.session.flash_msg = self.session.pop('flash_msg', '') 216 | now = timegm( gmtime() ) 217 | try: 218 | atime = int( self.session['atime'] ) 219 | except ValueError: 220 | self.session.end() 221 | return 222 | if now - atime > SESSION_TTL: 223 | self.session.end() 224 | return 225 | if now - atime > SID_TTL: 226 | self.session.regen() 227 | 228 | -------------------------------------------------------------------------------- /DEONE/ohlc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4 import QtCore, QtGui 4 | 5 | from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as Canvas 6 | from matplotlib.figure import Figure 7 | from matplotlib.pyplot import setp 8 | from matplotlib.ticker import Formatter 9 | from matplotlib.dates import YearLocator, MonthLocator, WeekdayLocator, DayLocator, DateFormatter, drange, MONDAY, num2date, date2num 10 | 11 | import sqlite3 12 | import datetime 13 | 14 | class OHLCWidget(Canvas): 15 | def __init__(self, parent=None): 16 | from matplotlib import rcParams 17 | rcParams['font.size'] = 6 18 | 19 | self.figure = Figure(facecolor='w',edgecolor='w') 20 | self.figure.subplots_adjust(left=0.1, right=0.9, wspace=0.6) 21 | 22 | Canvas.__init__(self, self.figure) 23 | self.setParent(parent) 24 | self.figure.canvas.mpl_connect('motion_notify_event', self.onmove) 25 | 26 | Canvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) 27 | Canvas.updateGeometry(self) 28 | 29 | def sizeHint(self): 30 | w, h = self.get_width_height() 31 | return QtCore.QSize(w, h) 32 | 33 | def minimumSizeHint(self): 34 | return QtCore.QSize(10, 10) 35 | 36 | def onmove(self, event): 37 | if event.xdata == None or event.inaxes == self.info_axes: 38 | return 39 | #print(event.x, event.xdata, event.inaxes) 40 | ind = int(round(event.xdata)) 41 | self.info(ind) 42 | 43 | def info(self, ind): 44 | print("call info", ind) 45 | if ind>=len(self.rows) or ind<0: return '' 46 | row = self.rows[ind] 47 | if row == None: 48 | return 49 | print(row) 50 | #today = datetime.datetime(1899,12,30)+datetime.timedelta(days=float(row[0])) 51 | y,m,d = row[0].split('-') 52 | today = datetime.datetime(int(y), int(m), int(d)) 53 | open = float(row[1]) 54 | high = float(row[2]) 55 | low = float(row[3]) 56 | close = float(row[4]) 57 | volume= int(float(row[5])) 58 | print(today, open, high, low, close, volume) 59 | 60 | self.info_axes.clear() 61 | self.info_axes.text(0.1, 0.95, 'Date') 62 | self.info_axes.text(0.15, 0.93, today.strftime("%Y%m%d"), color='b') 63 | self.info_axes.text(0.1, 0.90, 'Open') 64 | self.info_axes.text(0.15, 0.88, open, color='b') 65 | self.info_axes.text(0.1, 0.85, 'High') 66 | self.info_axes.text(0.15, 0.83, high, color='b') 67 | self.info_axes.text(0.1, 0.80, 'Low') 68 | self.info_axes.text(0.15, 0.78, low, color='b') 69 | self.info_axes.text(0.1, 0.75, 'Close') 70 | self.info_axes.text(0.15, 0.73, close, color='b') 71 | self.info_axes.text(0.1, 0.70, 'Volume') 72 | self.info_axes.text(0.15, 0.68, volume, color='b') 73 | self.info_axes.set_xticklabels([]) 74 | self.info_axes.set_yticklabels([]) 75 | 76 | self.figure.canvas.draw() 77 | 78 | def query(self, sql, parameters=None): 79 | self.figure.clear() 80 | 81 | self.vol_axes = self.figure.add_axes([0.06, 0.01, 0.87, 0.24], axisbg='#cccccc', autoscale_on=True) 82 | self.ohlc_axes = self.figure.add_axes([0.06, 0.25, 0.87, 0.74], axisbg='#cccccc', autoscale_on=True) 83 | self.info_axes = self.figure.add_axes([0.93, 0.01, 0.06, 0.98], axisbg='#cccccc', autoscale_on=True) 84 | 85 | # select data from sqlite3 db 86 | deone_db = r'data/DEONE.db' 87 | con = sqlite3.connect(deone_db) 88 | con.text_factory=str 89 | cur = con.cursor() 90 | #t = u'深发展A'.encode('utf-8') 91 | #cur.execute(u'select * from StockHist where 股票名称=?'.encode('utf-8'), (t,)) 92 | #cur.execute('select * from StockHist') 93 | cur.execute(sql, parameters) 94 | 95 | dates = [] 96 | ind = 0 97 | self.rows = [] 98 | for ind in xrange(100): 99 | row = cur.fetchone() 100 | if row == None: 101 | break 102 | #print(row) 103 | self.rows += [row] 104 | 105 | #today = datetime.datetime(1899,12,30)+datetime.timedelta(days=float(row[0])) 106 | y,m,d = row[0].split('-') 107 | today = datetime.datetime(int(y), int(m), int(d)) 108 | dates += [today] 109 | open = float(row[1]) 110 | high = float(row[2]) 111 | low = float(row[3]) 112 | close = float(row[4]) 113 | volume= int(float(row[5])) 114 | #print(ind, today, open, high, low, close, volume) 115 | 116 | if close>open: 117 | col = "r" 118 | elif close=len(self.dates) or ind<0: return '' 156 | s = self.dates[ind].strftime(self.fmt) 157 | #print(x, ind, s) 158 | return s 159 | 160 | #=============================================================================== 161 | # Example 162 | #=============================================================================== 163 | if __name__ == '__main__': 164 | import sys 165 | from PyQt4.QtGui import QMainWindow, QApplication 166 | 167 | class ApplicationWindow(QMainWindow): 168 | def __init__(self): 169 | QMainWindow.__init__(self) 170 | self.ohlc = OHLCWidget(self) 171 | self.ohlc.setFocus() 172 | self.setCentralWidget(self.ohlc) 173 | code ='1.0' 174 | self.ohlc.query(u'select 日期,开盘,最高,最低,收盘,成交量 from StockHist where 股票代码=?'.encode('utf-8'), (code,)) 175 | 176 | 177 | app = QApplication(sys.argv) 178 | win = ApplicationWindow() 179 | win.show() 180 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /DEONE/deone.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'DEONE.ui' 4 | # 5 | # Created: Mon Aug 02 09:11:58 2010 6 | # by: PyQt4 UI code generator 4.5.4 7 | # 8 | # WARNING! All changes made in this file will be lost! 9 | 10 | from PyQt4 import QtCore, QtGui 11 | import deone_rc 12 | 13 | from trading import TradingWidget 14 | from sector import SectorWidget 15 | from stock import StockWidget 16 | from market import MarketWidget 17 | 18 | class Ui_MainWindow(QtGui.QMainWindow): 19 | def __init__(self, parent=None): 20 | QtGui.QMainWindow.__init__(self) 21 | 22 | self.setObjectName("MainWindow") 23 | sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) 24 | sizePolicy.setHorizontalStretch(0) 25 | sizePolicy.setVerticalStretch(0) 26 | self.setSizePolicy(sizePolicy) 27 | self.setDocumentMode(False) 28 | 29 | self.menubar = QtGui.QMenuBar(self) 30 | self.menubar.setObjectName("menubar") 31 | self.menuView = QtGui.QMenu(self.menubar) 32 | self.menuView.setObjectName("menuView") 33 | self.setMenuBar(self.menubar) 34 | self.statusbar = QtGui.QStatusBar(self) 35 | self.statusbar.setSizeGripEnabled(True) 36 | self.statusbar.setObjectName("statusbar") 37 | self.setStatusBar(self.statusbar) 38 | self.toolBar = QtGui.QToolBar(self) 39 | self.toolBar.setObjectName("toolBar") 40 | self.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) 41 | self.actionStock = QtGui.QAction(self) 42 | icon = QtGui.QIcon() 43 | icon.addPixmap(QtGui.QPixmap(":/icons/image/icons/log.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 44 | self.actionStock.setIcon(icon) 45 | self.actionStock.setObjectName("actionStock") 46 | self.actionSector = QtGui.QAction(self) 47 | icon1 = QtGui.QIcon() 48 | icon1.addPixmap(QtGui.QPixmap(":/icons/image/icons/misc.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 49 | self.actionSector.setIcon(icon1) 50 | self.actionSector.setObjectName("actionSector") 51 | self.actionTrading = QtGui.QAction(self) 52 | icon2 = QtGui.QIcon() 53 | icon2.addPixmap(QtGui.QPixmap(":/icons/image/icons/kchart.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 54 | self.actionTrading.setIcon(icon2) 55 | self.actionTrading.setObjectName("actionTrading") 56 | self.actionMarket = QtGui.QAction(self) 57 | icon3 = QtGui.QIcon() 58 | icon3.addPixmap(QtGui.QPixmap(":/icons/image/icons/terminal.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 59 | self.actionMarket.setIcon(icon3) 60 | self.actionMarket.setObjectName("actionMarket") 61 | self.actionQuit = QtGui.QAction(self) 62 | icon5 = QtGui.QIcon() 63 | icon5.addPixmap(QtGui.QPixmap(":/icons/image/icons/exit.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 64 | self.actionQuit.setIcon(icon5) 65 | self.actionQuit.setObjectName("actionQuit") 66 | self.menuView.addAction(self.actionTrading) 67 | self.menuView.addAction(self.actionStock) 68 | self.menuView.addAction(self.actionSector) 69 | self.menuView.addAction(self.actionMarket) 70 | self.menuView.addSeparator() 71 | self.menuView.addAction(self.actionQuit) 72 | self.menubar.addAction(self.menuView.menuAction()) 73 | self.toolBar.addAction(self.actionTrading) 74 | self.toolBar.addAction(self.actionStock) 75 | self.toolBar.addAction(self.actionSector) 76 | self.toolBar.addAction(self.actionMarket) 77 | self.toolBar.addSeparator() 78 | self.toolBar.addAction(self.actionQuit) 79 | 80 | self.connect(self.actionTrading, QtCore.SIGNAL("triggered()"), self.doTabTrading) 81 | self.connect(self.actionStock, QtCore.SIGNAL("triggered()"), self.doTabStock) 82 | self.connect(self.actionSector, QtCore.SIGNAL("triggered()"), self.doTabSector) 83 | self.connect(self.actionMarket, QtCore.SIGNAL("triggered()"), self.doTabMarket) 84 | self.connect(self.actionQuit, QtCore.SIGNAL("triggered()"), self.doQuit) 85 | 86 | self.tabWidget = QtGui.QTabWidget(self) 87 | self.tabWidget.setTabsClosable(False) 88 | self.tabWidget.setObjectName("tabWidget") 89 | 90 | self.tabTrading = TradingWidget(self.tabWidget) 91 | self.tabWidget.addTab(self.tabTrading, "") 92 | 93 | self.tabSector = SectorWidget(self.tabWidget) 94 | self.tabWidget.addTab(self.tabSector, "") 95 | 96 | self.tabStock = StockWidget(self.tabWidget) 97 | self.tabWidget.addTab(self.tabStock, "") 98 | 99 | self.tabMarket = MarketWidget(self.tabWidget) 100 | self.tabWidget.addTab(self.tabMarket, "") 101 | self.tabWidget.setCurrentIndex(0) 102 | 103 | self.setCentralWidget(self.tabWidget) 104 | 105 | self.retranslateUi() 106 | QtCore.QMetaObject.connectSlotsByName(self) 107 | 108 | def retranslateUi(self): 109 | self.setWindowTitle(QtGui.QApplication.translate("MainWindow", "DEONE", None, QtGui.QApplication.UnicodeUTF8)) 110 | self.menuView.setTitle(QtGui.QApplication.translate("MainWindow", "功能", None, QtGui.QApplication.UnicodeUTF8)) 111 | self.toolBar.setWindowTitle(QtGui.QApplication.translate("MainWindow", "工具条", None, QtGui.QApplication.UnicodeUTF8)) 112 | self.actionStock.setText(QtGui.QApplication.translate("MainWindow", "股票", None, QtGui.QApplication.UnicodeUTF8)) 113 | self.actionSector.setText(QtGui.QApplication.translate("MainWindow", "板块", None, QtGui.QApplication.UnicodeUTF8)) 114 | self.actionTrading.setText(QtGui.QApplication.translate("MainWindow", "交易", None, QtGui.QApplication.UnicodeUTF8)) 115 | self.actionMarket.setText(QtGui.QApplication.translate("MainWindow", "大盘", None, QtGui.QApplication.UnicodeUTF8)) 116 | 117 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabTrading), QtGui.QApplication.translate("MainWindow", "交易", None, QtGui.QApplication.UnicodeUTF8)) 118 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabSector), QtGui.QApplication.translate("MainWindow", "板块", None, QtGui.QApplication.UnicodeUTF8)) 119 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabStock), QtGui.QApplication.translate("MainWindow", "股票", None, QtGui.QApplication.UnicodeUTF8)) 120 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabMarket), QtGui.QApplication.translate("MainWindow", "大盘", None, QtGui.QApplication.UnicodeUTF8)) 121 | self.actionQuit.setText(QtGui.QApplication.translate("MainWindow", "退出", None, QtGui.QApplication.UnicodeUTF8)) 122 | 123 | def doTabTrading(self): 124 | print("called doTabTrading") 125 | self.tabWidget.setCurrentIndex(1) 126 | 127 | def doTabStock(self): 128 | print("called doTabStock") 129 | self.tabWidget.setCurrentIndex(2) 130 | 131 | def doTabSector(self): 132 | print("called doTabSector") 133 | self.tabWidget.setCurrentIndex(3) 134 | 135 | def doTabMarket(self): 136 | print("called doTabMarket") 137 | self.tabWidget.setCurrentIndex(4) 138 | 139 | def doQuit(self): 140 | print("called doQuit") 141 | self.close() 142 | 143 | #=============================================================================== 144 | # Example 145 | #=============================================================================== 146 | if __name__ == "__main__": 147 | import sys 148 | 149 | app = QtGui.QApplication(sys.argv) 150 | window = Ui_MainWindow() 151 | window.showFullScreen() 152 | window.show() 153 | sys.exit(app.exec_()) 154 | -------------------------------------------------------------------------------- /DEONE/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4 import QtCore, QtGui 4 | from deone import Ui_MainWindow 5 | import os 6 | import simplejson 7 | import sqlite3 8 | 9 | host = 'de-one.appspot.com' 10 | #host = 'localhost:8080' 11 | 12 | deone_db = 'data/DEONE.db' 13 | 14 | tables = ["Market", "Suggestion", "StockHist", "StockCategory", "CategoryHist",] 15 | 16 | class LoginWindow(QtGui.QMainWindow): 17 | def __init__(self): 18 | QtGui.QMainWindow.__init__(self) 19 | 20 | self.setGeometry(QtCore.QRect(0, 0, 200, 120)) 21 | 22 | mainLayout = QtGui.QVBoxLayout() 23 | 24 | hLayoutUser = QtGui.QHBoxLayout() 25 | self.label_user = QtGui.QLabel(self) 26 | hLayoutUser.addWidget(self.label_user) 27 | self.lineEdit_user = QtGui.QLineEdit(self) 28 | self.lineEdit_user.setText("test") 29 | hLayoutUser.addWidget(self.lineEdit_user) 30 | mainLayout.addLayout(hLayoutUser) 31 | 32 | hLayoutPass = QtGui.QHBoxLayout() 33 | self.label_pass = QtGui.QLabel(self) 34 | hLayoutPass.addWidget(self.label_pass) 35 | self.lineEdit_pass = QtGui.QLineEdit(self) 36 | self.lineEdit_pass.setEchoMode(QtGui.QLineEdit.Password) 37 | self.lineEdit_pass.setText("test") 38 | hLayoutPass.addWidget(self.lineEdit_pass) 39 | mainLayout.addLayout(hLayoutPass) 40 | 41 | hLayoutCmd = QtGui.QHBoxLayout() 42 | self.pushButtonLogin = QtGui.QPushButton(self) 43 | hLayoutCmd.addWidget(self.pushButtonLogin) 44 | self.pushButtonExit = QtGui.QPushButton(self) 45 | hLayoutCmd.addWidget(self.pushButtonExit) 46 | 47 | self.label_msg = QtGui.QLabel(self) 48 | mainLayout.addWidget(self.label_msg) 49 | 50 | self.progbar = QtGui.QProgressBar(self) 51 | self.progbar.setProperty("value",QtCore.QVariant(0)) 52 | mainLayout.addWidget(self.progbar) 53 | 54 | mainLayout.addLayout(hLayoutCmd) 55 | 56 | widget = QtGui.QWidget() 57 | widget.setLayout(mainLayout) 58 | self.setCentralWidget(widget) 59 | 60 | self.retranslateUi() 61 | QtCore.QObject.connect(self.pushButtonExit, QtCore.SIGNAL("pressed()"), self.doExit) 62 | QtCore.QObject.connect(self.pushButtonLogin, QtCore.SIGNAL("pressed()"), self.doLogin) 63 | 64 | self.progbar.hide() 65 | self.center() 66 | 67 | def retranslateUi(self): 68 | self.label_user.setText(QtGui.QApplication.translate("MainWindow", "用户:", None, QtGui.QApplication.UnicodeUTF8)) 69 | self.label_pass.setText(QtGui.QApplication.translate("MainWindow", "密码:", None, QtGui.QApplication.UnicodeUTF8)) 70 | self.pushButtonLogin.setText(QtGui.QApplication.translate("MainWindow", "登入", None, QtGui.QApplication.UnicodeUTF8)) 71 | self.pushButtonExit.setText(QtGui.QApplication.translate("MainWindow", "退出", None, QtGui.QApplication.UnicodeUTF8)) 72 | 73 | def doExit(self): 74 | print("called doExit") 75 | self.close() 76 | 77 | def doLogin(self): 78 | print("called doLogin") 79 | usr=str(self.lineEdit_user.text()) 80 | pwd=str(self.lineEdit_pass.text()) 81 | if self.update(usr, pwd): 82 | self.showDeone() 83 | else: 84 | self.label_msg.setText("user can not login.") 85 | 86 | def showDeone(self): 87 | self.main = Ui_MainWindow() 88 | self.main.showFullScreen() 89 | self.main.show() 90 | self.close() 91 | 92 | def center(self): 93 | screen = QtGui.QDesktopWidget().screenGeometry() 94 | size = self.geometry() 95 | print(screen, size) 96 | self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) /2) 97 | 98 | def update(self, usr, pwd): 99 | print(usr, pwd) 100 | #1. get the version 101 | from client import App3Client 102 | from datetime import datetime 103 | 104 | tm_begin = datetime.now() 105 | 106 | fn_rev = 'data/rev.json' 107 | self.progbar.reset() 108 | self.progbar.show() 109 | self.progbar.setRange(0, 100) 110 | self.label_msg.setText("checking the latest version...") 111 | c = App3Client(host, usr, pwd) 112 | # 1. list all packages 113 | s = c.list('Deone') 114 | if s is None: 115 | ret = False 116 | else: 117 | # get local packages 118 | local_items = [] 119 | if os.path.exists(fn_rev): 120 | local_v = simplejson.load(open(fn_rev)) 121 | for local_item in local_v: 122 | local_items += [local_item['id']] 123 | 124 | #3. get deone package 125 | self.label_msg.setText("updating package...") 126 | prog_val = 1 127 | v = simplejson.loads(s) 128 | for item in v: 129 | id = item['id'] 130 | if id not in local_items: 131 | self.label_msg.setText("updating package %s...(%d/%d)" %(id, prog_val, len(v))) 132 | self.progbar.setValue(prog_val*100/len(v)) 133 | s = c.get('Deone', id) 134 | prog_val += 1 135 | if s is not None: 136 | root = simplejson.loads(s) 137 | #admin.json2sqlite3(root["data"] ) 138 | self.json2sqlite3(root["data"] ) 139 | #4. update local version 140 | simplejson.dump(v, open(fn_rev, 'w')) 141 | ret = True 142 | tm_end = datetime.now() 143 | self.label_msg.setText("package update completed. %d"%(tm_end - tm_begin).seconds) 144 | self.progbar.hide() 145 | return ret 146 | 147 | def json2sqlite3(self, str): 148 | con = sqlite3.connect(deone_db) 149 | obj = simplejson.loads(str) 150 | for tbl in tables: 151 | if obj.has_key(tbl): 152 | self.obj2table(con, tbl, obj[tbl]) 153 | con.close() 154 | 155 | def obj2table(self, con, tbl, obj): 156 | cur = con.cursor() 157 | ncols = obj["ncols"] 158 | nrows = obj["nrows"] 159 | cells = obj["cells"] 160 | #print(ncols, nrows, cells) 161 | for rownum in range(nrows): 162 | t = [] 163 | for colnum in range(ncols): 164 | #print(rownum, colnum) 165 | idx = rownum*ncols + colnum 166 | t += [cells[idx]] 167 | #print(idx, cells[idx]) 168 | if rownum == 0: 169 | s_cols = "" 170 | for s_col in t: 171 | s_cols += s_col + " text," 172 | s = '''create table %s(%s)'''%(tbl, s_cols.rstrip(',')) 173 | try: 174 | cur.execute(s) 175 | con.commit() 176 | except sqlite3.OperationalError, e: 177 | print(e) 178 | else: 179 | s_cols = ("?,"*ncols) 180 | s = '''insert into %s values (%s)'''%(tbl, s_cols.rstrip(',')) 181 | #print(s) 182 | cur.execute(s, t) 183 | con.commit() 184 | cur.close() 185 | 186 | #=============================================================================== 187 | # Example 188 | #=============================================================================== 189 | if __name__ == '__main__': 190 | import os, sys 191 | if not os.path.exists('data'): os.mkdir('data') 192 | if os.path.exists('__local'): 193 | host = 'localhost:8080' 194 | app = QtGui.QApplication(sys.argv) 195 | window = LoginWindow() 196 | window.show() 197 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /appspot/restlet/parsutil.py: -------------------------------------------------------------------------------- 1 | import cgi 2 | import doctest 3 | import logging 4 | import os 5 | import re 6 | 7 | logger = logging.getLogger() 8 | logger.setLevel(getattr(logging, os.environ.get('LOGLEVEL', 'WARNING'))) 9 | 10 | class UrlParser(object): 11 | """ Parse a URL path and perform appropriate an callback on regex-matching. 12 | 13 | Instantiate h with a prefix (to be matched, but ignored if it matches), 14 | followed by as many (regex, callback) pairs as needed. 15 | Then, call h.process(path): if the path matches the prefix, then 16 | each regex is tried *IN ORDER* on the rest of the path, and, 17 | upon the first match if any, the corresponding callback gets called 18 | (and its results returned). 19 | If the prefix does not match, or none of the regexes does, then 20 | method call h.process(path) returns None. 21 | The callback is passed *NAMED* arguments (only!) corresponding to 22 | the positional groups matched in the prefix, augmented or overridden 23 | by those matched in the specific regex that matched after that. 24 | So for example: 25 | >>> def show(**k): print sorted(k.items()) 26 | >>> h = UrlParser(r'/(?P\w+)/', 27 | ... (r'(?P\d+)', show), 28 | ... (r'(?P[^/]*)', show), 29 | ... ) 30 | >>> h.process('/zipzop/23/whatever') 31 | [('bar', '23'), ('foo', 'zipzop')] 32 | >>> h.process('/zipzop/whoo/whatever') 33 | [('foo', 'whoo')] 34 | 35 | You can also change the prefix by passing a prefix to .process(...) [the 36 | new prefix-to-ignore is then remembered in lieu of the previous one]. 37 | 38 | >>> h.prefix.pattern 39 | '/(?P\\\\w+)/' 40 | >>> h.process('/zipzop/whoo/whatever', prefix='/') 41 | [('foo', 'zipzop')] 42 | >>> h.process('/zipzop/whoo/whatever') 43 | [('foo', 'zipzop')] 44 | >>> h.prefix.pattern 45 | '/' 46 | 47 | The h.prefix attribute is exposed, and it's a RE object. 48 | """ 49 | 50 | def __init__(self, prefix, *args): 51 | """ Takes a prefix to be ignored and 0+ (regex, callback) pair args. 52 | 53 | Args: 54 | prefix: a string regex pattern 55 | args: 0+ pairs (regex_pattern, callback) [each a string + a callable] 56 | """ 57 | self.prefix = re.compile(prefix or '') 58 | logging.debug('prefix: %r', prefix) 59 | self.callbacks = [] 60 | for pattern, callback in args: 61 | logging.debug('%r -> %r', pattern, callback) 62 | self.callbacks.append((re.compile(pattern), callback)) 63 | 64 | def process(self, path, prefix=None): 65 | """ Match the path to one of the regexs and call the appropriate callback. 66 | 67 | Args: 68 | path: a string URL (complete path) to parse 69 | prefix: if not None, a RE pattern string to change self.prefix from now on 70 | Returns: 71 | the result of the appropriate callback, or None if no match 72 | """ 73 | if prefix is not None and prefix != self.prefix.pattern: 74 | self.prefix = re.compile(prefix) 75 | prefix_mo = self.prefix.match(path) 76 | if prefix_mo is None: 77 | logging.debug('No prefix match for %r (%r)', path, self.prefix) 78 | return None 79 | pathrest = path[prefix_mo.end():] 80 | logging.debug('Matching %r...', pathrest) 81 | for regex, callback in self.callbacks: 82 | mo = regex.match(pathrest) 83 | if mo: 84 | logging.debug('Matched %r, calling %r', regex, callback) 85 | named_args = prefix_mo.groupdict() 86 | named_args.update(mo.groupdict()) 87 | return callback(**named_args) 88 | logging.debug('No match for %r', pathrest) 89 | return None 90 | 91 | 92 | class RestUrlParser(UrlParser): 93 | """ Specifically dispatches on the REs associated with REST-shaped URLs. 94 | 95 | Note that h.process only takes an URL *path*, NOT the rest of the URL (no 96 | protocol, no host, no query). 97 | 98 | >>> h = RestUrlParser('') 99 | >>> h.process('/$foobar') 100 | ('special', '$foobar') 101 | >>> h.process('/foobar') 102 | ('model', 'foobar') 103 | >>> h.process('/$foobar/zak/') 104 | ('special_method', '$foobar', 'zak') 105 | >>> h.process('/foobar/zak/') 106 | ('model_method', 'foobar', 'zak') 107 | >>> h.process('/foobar/23/') 108 | ('model_strid', 'foobar', '23') 109 | >>> h.process('/foobar/23/blop') 110 | ('model_strid_method', 'foobar', '23', 'blop') 111 | >>> h.process('') 112 | >>> h.process('////////') 113 | >>> 114 | """ 115 | 116 | @staticmethod 117 | def _doprefix(prefix): 118 | if prefix is None: return None 119 | prefix = prefix.strip('/') 120 | if prefix: return '/%s/' % prefix 121 | else: return '/' 122 | 123 | def process(self, path, prefix=None): 124 | return UrlParser.process(self, path, self._doprefix(prefix)) 125 | 126 | def __init__(self, prefix=None, **overrides): 127 | """ Set the prefix-to-ignore, optionally override methods. 128 | 129 | Args: 130 | prefix: a string regex pattern (or None, default) 131 | overrides: 0+ named arguments; values are callables to override the 132 | methods RestUrlParser provides (which just return tuples of strings), 133 | and each such callable must be signature-compatible with the 134 | corresponding named method. The methods & signaturs are: 135 | do_special(special) 136 | do_model(model) 137 | do_special_method(special, method) 138 | do_model_method(model, method) 139 | do_model_strid(model, strid) 140 | do_model_strid_method(model, strid, method) 141 | 142 | The *names* (not necessarily the *order*) of the arguments matter. 143 | 144 | The values of all arguments are strings (the substrings of the 145 | incoming path that match the respective items of the REST URL): 146 | strid is always 1+ digits; special is '$' + a valid identifier; 147 | model and method are identifiers. 148 | """ 149 | # let each method be overridden (in the instance) by caller at ctor-time 150 | self.__dict__.update(overrides) 151 | 152 | # prefix must always absorb leading and trailing / 153 | prefix = self._doprefix(prefix) 154 | 155 | # build URL regexes with corresponding names 156 | urls = [] 157 | def addurl(name, regex): urls.append((regex, getattr(self, 'do_'+name))) 158 | 159 | sr_method = r'/(?P\w+)' 160 | sr_strid = r'/(?P\d+)' 161 | 162 | # special_method must be before special (ie. special_method > special) 163 | re_special = r'(?P\$\w+)/?' 164 | re_special_method = re_special + sr_method 165 | addurl('special_method', re_special_method) 166 | addurl('special', re_special) 167 | 168 | # model_strid_method > model_strid > model_method > model 169 | re_model = r'(?P\w+)/?' 170 | re_model_method = re_model + sr_method 171 | re_model_strid = re_model + sr_strid 172 | re_model_strid_method = re_model_strid + sr_method 173 | addurl('model_strid_method', re_model_strid_method) 174 | addurl('model_strid', re_model_strid) 175 | addurl('model_method', re_model_method) 176 | addurl('model', re_model) 177 | 178 | UrlParser.__init__(self, prefix, *urls) 179 | 180 | def do_special(self, special): 181 | return 'special', special 182 | def do_model(self, model): 183 | return 'model', model 184 | def do_special_method(self, special, method): 185 | return 'special_method', special, method 186 | def do_model_method(self, model, method): 187 | return 'model_method', model, method 188 | def do_model_strid(self, model, strid): 189 | return 'model_strid', model, strid 190 | def do_model_strid_method(self, model, strid, method): 191 | return 'model_strid_method', model, strid, method 192 | 193 | 194 | def _test(): 195 | import doctest 196 | numfailures, numtests = doctest.testmod() 197 | if numfailures == 0: 198 | print '%d tests passed successfully' % numtests 199 | # if there are any failures, doctest does its own reporting!-) 200 | 201 | if __name__ == "__main__": 202 | _test() 203 | -------------------------------------------------------------------------------- /DEONE/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PyQt4 import QtCore, QtGui 4 | from deone import Ui_MainWindow 5 | import os 6 | import simplejson 7 | import sqlite3 8 | import sys 9 | import string 10 | import xlrd 11 | 12 | host = 'de-one.appspot.com' 13 | #host = 'localhost:8080' 14 | 15 | 16 | fn_uprev = 'data/uprev.json' 17 | 18 | tables = { 19 | u"深成指399001.xls":"Market", 20 | u"BJS交易类型2.xls":"Suggestion", 21 | u"DBS交易类型1.xls":"Suggestion", 22 | u"NRA交易类型4.xls":"Suggestion", 23 | u"SDB交易类型3.xls":"Suggestion", 24 | u"个股历史数据.xls":"StockHist", 25 | u"大盘数据.xls":"Market", 26 | u"StockCategory.xls":"StockCategory", 27 | u"CategoryHist.xls":"CategoryHist", 28 | } 29 | 30 | from client import App3Client 31 | c = App3Client('de-one.appspot.com', 'test', 'test') 32 | 33 | class AdminWindow(QtGui.QMainWindow): 34 | def __init__(self): 35 | QtGui.QMainWindow.__init__(self) 36 | 37 | self.setGeometry(QtCore.QRect(0, 0, 320, 160)) 38 | 39 | mainLayout = QtGui.QVBoxLayout() 40 | 41 | hLayoutUser = QtGui.QHBoxLayout() 42 | self.label_user = QtGui.QLabel(self) 43 | hLayoutUser.addWidget(self.label_user) 44 | self.lineEdit_user = QtGui.QLineEdit(self) 45 | self.lineEdit_user.setText("test") 46 | hLayoutUser.addWidget(self.lineEdit_user) 47 | mainLayout.addLayout(hLayoutUser) 48 | 49 | hLayoutPass = QtGui.QHBoxLayout() 50 | self.label_pass = QtGui.QLabel(self) 51 | hLayoutPass.addWidget(self.label_pass) 52 | self.lineEdit_pass = QtGui.QLineEdit(self) 53 | self.lineEdit_pass.setEchoMode(QtGui.QLineEdit.Password) 54 | self.lineEdit_pass.setText("test") 55 | hLayoutPass.addWidget(self.lineEdit_pass) 56 | mainLayout.addLayout(hLayoutPass) 57 | 58 | hLayoutTag = QtGui.QHBoxLayout() 59 | self.label_tag = QtGui.QLabel(self) 60 | hLayoutTag.addWidget(self.label_tag) 61 | self.lineEdit_tag = QtGui.QLineEdit(self) 62 | self.lineEdit_pass.setEchoMode(QtGui.QLineEdit.Password) 63 | hLayoutTag.addWidget(self.lineEdit_tag) 64 | mainLayout.addLayout(hLayoutTag) 65 | 66 | hLayoutCmd = QtGui.QHBoxLayout() 67 | self.pushButtonUpload = QtGui.QPushButton(self) 68 | hLayoutCmd.addWidget(self.pushButtonUpload) 69 | self.pushButtonReset = QtGui.QPushButton(self) 70 | hLayoutCmd.addWidget(self.pushButtonReset) 71 | self.pushButtonExit = QtGui.QPushButton(self) 72 | hLayoutCmd.addWidget(self.pushButtonExit) 73 | 74 | self.label_msg = QtGui.QLabel(self) 75 | self.label_msg.setGeometry(QtCore.QRect(0, 0, 320, 20)) 76 | mainLayout.addWidget(self.label_msg) 77 | 78 | self.progbar = QtGui.QProgressBar(self) 79 | self.progbar.setProperty("value",QtCore.QVariant(0)) 80 | mainLayout.addWidget(self.progbar) 81 | 82 | mainLayout.addLayout(hLayoutCmd) 83 | 84 | widget = QtGui.QWidget() 85 | widget.setLayout(mainLayout) 86 | self.setCentralWidget(widget) 87 | 88 | self.retranslateUi() 89 | QtCore.QObject.connect(self.pushButtonExit, QtCore.SIGNAL("pressed()"), self.doExit) 90 | QtCore.QObject.connect(self.pushButtonUpload, QtCore.SIGNAL("pressed()"), self.doUpload) 91 | QtCore.QObject.connect(self.pushButtonReset, QtCore.SIGNAL("pressed()"), self.doReset) 92 | 93 | self.progbar.hide() 94 | self.center() 95 | 96 | def retranslateUi(self): 97 | self.label_user.setText(QtGui.QApplication.translate("MainWindow", "用户:", None, QtGui.QApplication.UnicodeUTF8)) 98 | self.label_pass.setText(QtGui.QApplication.translate("MainWindow", "密码:", None, QtGui.QApplication.UnicodeUTF8)) 99 | self.label_tag.setText(QtGui.QApplication.translate("MainWindow", "标记:", None, QtGui.QApplication.UnicodeUTF8)) 100 | self.pushButtonUpload.setText(QtGui.QApplication.translate("MainWindow", "上传", None, QtGui.QApplication.UnicodeUTF8)) 101 | self.pushButtonReset.setText(QtGui.QApplication.translate("MainWindow", "恢复", None, QtGui.QApplication.UnicodeUTF8)) 102 | self.pushButtonExit.setText(QtGui.QApplication.translate("MainWindow", "退出", None, QtGui.QApplication.UnicodeUTF8)) 103 | 104 | def doExit(self): 105 | print("called doExit") 106 | self.close() 107 | 108 | def doReset(self): 109 | print("called doReset") 110 | usr=str(self.lineEdit_user.text()) 111 | pwd=str(self.lineEdit_pass.text()) 112 | tag=str(self.lineEdit_tag.text()) 113 | if not self.reset(usr, pwd, tag): 114 | self.label_msg.setText("user can not reset.") 115 | 116 | def doUpload(self): 117 | print("called doUpload") 118 | usr=str(self.lineEdit_user.text()) 119 | pwd=str(self.lineEdit_pass.text()) 120 | tag=str(self.lineEdit_tag.text()) 121 | if not self.upload(usr, pwd, tag): 122 | self.label_msg.setText("user can not upload.") 123 | 124 | def center(self): 125 | screen = QtGui.QDesktopWidget().screenGeometry() 126 | size = self.geometry() 127 | print(screen, size) 128 | self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) /2) 129 | 130 | def reset(self, usr, pwd, tag): 131 | from client import App3Client 132 | from datetime import datetime 133 | 134 | tm_begin = datetime.now() 135 | self.progbar.reset() 136 | self.progbar.show() 137 | self.progbar.setRange(0, 100) 138 | self.label_msg.setText("resetting package...") 139 | c = App3Client(host, usr, pwd) 140 | 141 | items = {} 142 | if tag is None or len(tag) == 0: 143 | s = c.list('Deone') 144 | #print(s) 145 | if s is not None: 146 | items = simplejson.loads(s) 147 | else: 148 | root = simplejson.load(open(fn_uprev)) 149 | if root is not None: 150 | if root.has_key(tag): 151 | items = root[tag] 152 | prog_val = 1 153 | for item in items: 154 | id = item['id'] 155 | self.label_msg.setText("reseting package %s...(%d/%d)" %(id, prog_val, len(items))) 156 | self.progbar.setValue(prog_val*100/len(items)) 157 | s = c.delete('Deone', id) 158 | if s is None: 159 | ret = False 160 | break 161 | prog_val += 1 162 | 163 | ret = True 164 | 165 | tm_end = datetime.now() 166 | self.label_msg.setText("package reset completed. %d"%(tm_end - tm_begin).seconds) 167 | self.progbar.hide() 168 | return ret 169 | 170 | def upload(self, usr, pwd, tag): 171 | 172 | from client import App3Client 173 | from datetime import datetime 174 | 175 | tm_begin = datetime.now() 176 | 177 | self.progbar.reset() 178 | self.progbar.show() 179 | self.progbar.setRange(0, 100) 180 | c = App3Client(host, usr, pwd) 181 | 182 | self.label_msg.setText("uploading package...") 183 | 184 | ret = True 185 | self.uprev_ids = [] 186 | 187 | for fn,tbl in tables.items(): 188 | if not self.xls2obj(c, fn, tbl): 189 | ret = False 190 | break 191 | 192 | #print(self.uprev_ids) 193 | local_root = {} 194 | if os.path.exists(fn_uprev): 195 | local_root = simplejson.load(open(fn_uprev)) 196 | if local_root is not None: 197 | #print(local_root) 198 | if tag is None or len(tag) == 0: 199 | tag = datetime.now().strftime("%Y-%m-%d") 200 | if local_root.has_key(tag): 201 | local_root[tag] += self.uprev_ids 202 | else: 203 | local_root[tag] = self.uprev_ids 204 | simplejson.dump(local_root, open(fn_uprev, 'w')) 205 | 206 | tm_end = datetime.now() 207 | self.label_msg.setText("package upload completed. %d"%(tm_end - tm_begin).seconds) 208 | self.progbar.hide() 209 | return ret 210 | 211 | def xls2obj(self, c, fn, tbl): 212 | wb = xlrd.open_workbook(os.path.join(os.path.abspath("data"), fn)) 213 | sh = wb.sheet_by_index(0) 214 | 215 | #first line is columns 216 | cols = [] 217 | for colnum in range(sh.ncols): 218 | t = sh.cell_type(0, colnum) 219 | val = sh.cell_value(0, colnum) 220 | cols +=[val] 221 | cells = [] 222 | i_row = 0 223 | #sh.nrows = 5 224 | for rownum in xrange(1, sh.nrows): 225 | for colnum in range(sh.ncols): 226 | t = sh.cell_type(rownum, colnum) 227 | val = sh.cell_value(rownum, colnum) 228 | #print(rownum, colnum, t, val) 229 | if t == 3: 230 | dt = xlrd.xldate_as_tuple(val, 0) 231 | s_dt = "%04d-%02d-%02d"%(dt[0:3]) 232 | #print(s_dt) 233 | cells += [s_dt] 234 | else: 235 | try: 236 | f = float(val) 237 | i = int(f) 238 | if f == float(i): 239 | cells += ["%d" %(i)] 240 | else: 241 | cells += ["%.2f" %(f)] 242 | except ValueError: 243 | cells += [val] 244 | i_row += 1 245 | 246 | #print(sh.nrows, rownum, i_row) 247 | self.label_msg.setText("uploading package %s...(%d/%d)" %(fn, rownum, sh.nrows)) 248 | self.progbar.setValue(rownum*100/sh.nrows) 249 | 250 | if (i_row%5000==0) or (rownum == sh.nrows-1): 251 | obj = {"ncols":sh.ncols, "nrows":i_row+1, "cells":cols+cells} 252 | s_json = simplejson.dumps({tbl:obj}) 253 | #print(s_json) 254 | s = c.post('Deone', {"data": s_json}) 255 | #print(s) 256 | if s is None: 257 | return False 258 | else: 259 | obj = simplejson.loads(s) 260 | if obj is not None: 261 | self.uprev_ids += [{"id":obj["id"]}] 262 | cells = [] 263 | i_row = 0 264 | 265 | return True 266 | 267 | #=============================================================================== 268 | # Example 269 | #=============================================================================== 270 | if __name__ == '__main__': 271 | import os, sys 272 | if not os.path.exists('data'): os.mkdir('data') 273 | if os.path.exists('__local'): 274 | host = 'localhost:8080' 275 | app = QtGui.QApplication(sys.argv) 276 | window = AdminWindow() 277 | window.show() 278 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /appspot/restlet/restutil.py: -------------------------------------------------------------------------------- 1 | """ Utilities for REST CRUD support for GAE db models. 2 | 3 | Specifically, this module facilitates introspection about a data model built 4 | on GAE db -- a registry of what db.Model subclasses are made available for 5 | introspection and by what names, utilities to register and query about such 6 | classes 'in bulk', mapping of property values of instances of those classes 7 | from and to strings. Reference properties, in particular, are mapped to 8 | strings of the form Classname/ where id is a unique-within-class id 9 | usable for the get_by_id method of the corresponding class; 10 | "reverse-reference" properties are *not* supported for conversion to/from 11 | string. 12 | 13 | The conversion of property values to/from string is made by static methods 14 | named foo_to_string and foo_from_string (for a property class attribute 15 | named foo); this module offers facilities to make and install on the class 16 | object all such needed methods, but if the class itself explicitly chooses 17 | to define some methods with these names, those facilities will not override 18 | them (so each db.Model subclass gets a chance to special-case some or all 19 | of its instance's property attributes). The_from_string method is not 20 | 21 | The module also offers the ability to register and retrieve (by string 22 | names): 23 | -- 'special objects' (model-like, but with no entities) 24 | such a registration just creates a namespace (for registering methods on) 25 | which is represented as a dict 26 | -- 'methods' which can be registered as (any one of; for >1 register again) 27 | -- callable on a special object, 28 | -- callable on a model, 29 | -- callable on any entity of a model 30 | all such registrations require a callable taking named args which are 31 | lists coming from the cgi.parse_qs parsing of a query string; 32 | the callable object registered for a method that's registered as callable 33 | on any entity of a model also takes a first argument 'self' that is 34 | the specific entity on which it is being called. 35 | -- entry points to query all the methods callable on a special object, 36 | model, or, any entity of a given model 37 | 38 | """ 39 | import datetime 40 | import inspect 41 | import logging 42 | import sys 43 | 44 | from google.appengine.ext import db 45 | from google.appengine.api import users 46 | 47 | 48 | def id_of(x): 49 | """ Get the numeric ID given an instance x of a db.Model subclass. """ 50 | return x.key().id() 51 | 52 | def identity(x): return x 53 | identity = staticmethod(identity) 54 | 55 | def isProperty(x): 56 | """ Is class attribute x a 'real' property (not a reverse reference)? 57 | 58 | Args: 59 | x: a class attribute (from some db.Model subclass) 60 | Returns: 61 | True iff x's type is that of a "real" property (not a rev.ref.) 62 | """ 63 | return isinstance(x, db.Property 64 | ) and not isinstance(x, db._ReverseReferenceProperty) 65 | 66 | 67 | specials_registry = dict() 68 | def registerSpecialByName(name): 69 | if name in specials_registry: 70 | raise KeyError, 'Duplicate name %r for specials registry' % name 71 | specials_registry[name] = dict(_n=name) 72 | 73 | def specialFromName(name): 74 | """ Get special object with the given name (None if none). 75 | 76 | Args: 77 | name: a string that should be registered as name for a special object 78 | Returns: 79 | dict that's the special object thus named, None if none 80 | """ 81 | return specials_registry.get(name) 82 | 83 | def allSpecialNames(): 84 | """ Return a list of strings, all special object names in registry. """ 85 | return sorted(specials_registry) 86 | 87 | def registerSpecialMethod(special, name, method): 88 | if isinstance(special, str): 89 | spc = specialFromName(special) 90 | if spc is None: 91 | raise KeyError, 'No special %r' % special 92 | special = spc 93 | if name in special: 94 | raise KeyError, 'Duplicated method name %r for special %r' % ( 95 | name, special['_n']) 96 | special[name] = method 97 | 98 | def specialMethodFromName(special, name): 99 | if isinstance(special, str): 100 | special = specialFromName(special) 101 | if special is None: 102 | return None 103 | return special.get(name) 104 | 105 | 106 | model_class_registry = dict() 107 | 108 | def registerClassByName(cls, name=None): 109 | """ Register a db.Model subclass with the given name (def. its own name). """ 110 | if name is None: name = cls.__name__ 111 | if name in model_class_registry: 112 | raise KeyError, 'Duplicate name %r for model class registry' % name 113 | model_class_registry[name] = cls 114 | setattr(cls, '_n', name) 115 | 116 | def isModelClass(x): 117 | """ Is object x a subclass of db.Model? 118 | 119 | Args: 120 | x: any 121 | Returns: 122 | true iff x is a subclass of db.Model 123 | """ 124 | try: return issubclass(x, db.Model) 125 | except TypeError: return False 126 | 127 | def registerAllModelClasses(module_obj): 128 | """ Register non-private db.Model subclasses from the given module object. """ 129 | for name, cls in inspect.getmembers(module_obj, isModelClass): 130 | if name[0] != '_': 131 | registerClassByName(cls, name) 132 | 133 | def registerAllModelClassesFromModuleNamed(module_name): 134 | """ Register all db.Model subclasses from module w/given name. """ 135 | registerAllModelClasses(__import__(module_name)) 136 | 137 | def modelClassFromName(classname): 138 | """ Get the db.Model subclass with the given name (None if none). 139 | 140 | Only handles db.Model subclasses enregistered into model_class_registry. 141 | 142 | Args: 143 | classname: a string that should name a db.Model subclass 144 | Returns: 145 | class object with that name, or None if there's no such class 146 | """ 147 | return model_class_registry.get(classname) 148 | 149 | def nameFromModelClass(cls): 150 | """ Get the name a db.Model subclass is registered under (or None). """ 151 | return getattr(cls, '_n', None) 152 | 153 | def allModelClassNames(): 154 | """ Return a list of strings, all model class names in registry. """ 155 | return sorted(model_class_registry) 156 | 157 | def _getter(model, an): 158 | if isinstance(model, str): 159 | mdl = modelClassFromName(model) 160 | if mdl is None: 161 | raise KeyError, 'No model named %r' % model 162 | model = mdl 163 | mm = getattr(model, an, None) 164 | if mm is None: 165 | mm = dict() 166 | setattr(model, an, mm) 167 | 168 | def _registerMethod(model, name, method, _getter_an): 169 | model, mm = _getter(model, _getter_an) 170 | if name in mm: 171 | raise KeyError, 'Duplicate name %r for method in model %r' % (name, 172 | nameFromModelClass(model)) 173 | mm[name] = method 174 | 175 | def _methodByName(model, name, _getter_an): 176 | model, mm = _getter(model, _getter_an) 177 | return mm.get(name) 178 | 179 | def _allMethods(model, _getter_an): 180 | model, mm = _getter(model, _getter_an) 181 | return sorted(mm) 182 | 183 | def registerModelMethod(model, name, method): 184 | return _registerMethod(model, name, method, '_mm') 185 | 186 | def modelMethodByName(model, name): 187 | return _methodByName(model, name, '_mm') 188 | 189 | def allModelMethods(model): 190 | return _allMethods(model, '_mm') 191 | 192 | def registerInstanceMethod(model, name, method): 193 | return _registerMethod(model, name, method, '_im') 194 | 195 | def instanceMethodByName(model, name): 196 | return _methodByName(model, name, '_im') 197 | 198 | def allInstanceMethods(model): 199 | return _allMethods(model, '_im') 200 | 201 | 202 | def modelInstanceByClassAndId(s): 203 | """ Get a model instance given its class name and numeric ID, or None. 204 | 205 | Args: 206 | s: str of the form 'Classname/1234' 207 | Returns: 208 | model instance from the class of that name, with that ID (or None) 209 | """ 210 | classname, theid = s.split('/') 211 | theclass = modelClassFromName(classname) 212 | if theclass is None: return None 213 | return theclass.get_by_id(int(theid)) 214 | 215 | def classAndIdFromModelInstance(x, classname=None): 216 | """ Get a string with class name and numeric ID given a model instance. 217 | 218 | Args: 219 | x: a model instance or None 220 | Returns: 221 | str of the form 'Classname/1234' (or None if x is None) 222 | """ 223 | if x is None: return None 224 | if classname is None: classname = type(x).__name__ 225 | theclass = modelClassFromName(classname) 226 | if theclass is not type(x): return None 227 | return '%s/%s' % (classname, id_of(x)) 228 | 229 | 230 | DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' 231 | 232 | def datetimeFromString(s): 233 | """ Get a datetime object given a str ('right now' for empty str). 234 | 235 | As per appengine convention, all datetime objs must be UTC. 236 | 237 | Args: 238 | s: str in DATETIME_FORMAT or '' 239 | Returns: 240 | appropriate datetime object 241 | """ 242 | if s: 243 | return datetime.datetime.strptime(s, DATETIME_FORMAT) 244 | else: 245 | return datetime.datetime.now() 246 | 247 | 248 | def stringFromDatetime(dt): 249 | """ Get an appropriately formatted str given a datetime object. 250 | 251 | Args: 252 | dt: datetime instance 253 | Returns: 254 | str formatted as per DATETIME_FORMAT 255 | """ 256 | return dt.strftime(DATETIME_FORMAT) 257 | 258 | 259 | # mapping from property types to appropriate str->value function if any 260 | # property types not in the mapping must accept a properly formatted str 261 | setter_registry = { 262 | db.BooleanProperty: 'False'.__ne__, 263 | db.DateTimeProperty: staticmethod(datetimeFromString), 264 | db.IntegerProperty: int, 265 | db.FloatProperty: float, 266 | db.ReferenceProperty: staticmethod(modelInstanceByClassAndId), 267 | db.StringListProperty: str.split, 268 | db.UserProperty: users.User, 269 | } 270 | 271 | # mapping from property types to appropriate value->str function if any 272 | # str(value) is used for property types that are not in the mapping 273 | getter_registry = { 274 | db.DateTimeProperty: staticmethod(stringFromDatetime), 275 | db.ReferenceProperty: staticmethod(classAndIdFromModelInstance), 276 | db.StringListProperty: ' '.join, 277 | } 278 | 279 | def allProperties(cls): 280 | """ Get all (name, value) pairs of properties given a db.Model subclass. 281 | 282 | Args: 283 | cls: a class object (a db.Model subclass) 284 | Returns: 285 | list of (name, value) pairs of properties of that class 286 | """ 287 | return inspect.getmembers(cls, isProperty) 288 | 289 | 290 | def addHelperMethods(cls): 291 | """ Add _from_string and _to_string methods to a db.Model subclass. 292 | 293 | Args: 294 | cls: a class object (db.Model subclass), adds methods to it. 295 | """ 296 | logging.info('decorating model %r', cls) 297 | props = allProperties(cls) 298 | for name, value in props: 299 | fs_name = name + '_from_string' 300 | if not hasattr(cls, fs_name): 301 | setter = setter_registry.get(type(value), identity) 302 | setattr(cls, fs_name, setter) 303 | # logging.info('added %r: %r', fs_name, setter) 304 | ts_name = name + '_to_string' 305 | if not hasattr(cls, ts_name): 306 | getter = getter_registry.get(type(value), str) 307 | setattr(cls, ts_name, getter) 308 | # logging.info('added %r: %r', ts_name, getter) 309 | 310 | def decorateModuleNamed(module_name): 311 | """ Do all needed work for non-private model classes in module thus named. """ 312 | module_obj = __import__(module_name) 313 | for name, cls in inspect.getmembers(module_obj, isModelClass): 314 | if name[0] != '_': 315 | registerClassByName(cls, name) 316 | addHelperMethods(cls) 317 | -------------------------------------------------------------------------------- /appspot/simplejson/decoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of JSONDecoder 3 | """ 4 | import re 5 | import sys 6 | 7 | from simplejson.scanner import Scanner, pattern 8 | try: 9 | from simplejson._speedups import scanstring as c_scanstring 10 | except ImportError: 11 | pass 12 | 13 | FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL 14 | 15 | def _floatconstants(): 16 | import struct 17 | import sys 18 | _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') 19 | if sys.byteorder != 'big': 20 | _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] 21 | nan, inf = struct.unpack('dd', _BYTES) 22 | return nan, inf, -inf 23 | 24 | NaN, PosInf, NegInf = _floatconstants() 25 | 26 | 27 | def linecol(doc, pos): 28 | lineno = doc.count('\n', 0, pos) + 1 29 | if lineno == 1: 30 | colno = pos 31 | else: 32 | colno = pos - doc.rindex('\n', 0, pos) 33 | return lineno, colno 34 | 35 | 36 | def errmsg(msg, doc, pos, end=None): 37 | lineno, colno = linecol(doc, pos) 38 | if end is None: 39 | return '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos) 40 | endlineno, endcolno = linecol(doc, end) 41 | return '%s: line %d column %d - line %d column %d (char %d - %d)' % ( 42 | msg, lineno, colno, endlineno, endcolno, pos, end) 43 | 44 | 45 | _CONSTANTS = { 46 | '-Infinity': NegInf, 47 | 'Infinity': PosInf, 48 | 'NaN': NaN, 49 | 'true': True, 50 | 'false': False, 51 | 'null': None, 52 | } 53 | 54 | def JSONConstant(match, context, c=_CONSTANTS): 55 | s = match.group(0) 56 | fn = getattr(context, 'parse_constant', None) 57 | if fn is None: 58 | rval = c[s] 59 | else: 60 | rval = fn(s) 61 | return rval, None 62 | pattern('(-?Infinity|NaN|true|false|null)')(JSONConstant) 63 | 64 | 65 | def JSONNumber(match, context): 66 | match = JSONNumber.regex.match(match.string, *match.span()) 67 | integer, frac, exp = match.groups() 68 | if frac or exp: 69 | fn = getattr(context, 'parse_float', None) or float 70 | res = fn(integer + (frac or '') + (exp or '')) 71 | else: 72 | fn = getattr(context, 'parse_int', None) or int 73 | res = fn(integer) 74 | return res, None 75 | pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(JSONNumber) 76 | 77 | 78 | STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) 79 | BACKSLASH = { 80 | '"': u'"', '\\': u'\\', '/': u'/', 81 | 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', 82 | } 83 | 84 | DEFAULT_ENCODING = "utf-8" 85 | 86 | def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match): 87 | if encoding is None: 88 | encoding = DEFAULT_ENCODING 89 | chunks = [] 90 | _append = chunks.append 91 | begin = end - 1 92 | while 1: 93 | chunk = _m(s, end) 94 | if chunk is None: 95 | raise ValueError( 96 | errmsg("Unterminated string starting at", s, begin)) 97 | end = chunk.end() 98 | content, terminator = chunk.groups() 99 | if content: 100 | if not isinstance(content, unicode): 101 | content = unicode(content, encoding) 102 | _append(content) 103 | if terminator == '"': 104 | break 105 | elif terminator != '\\': 106 | if strict: 107 | raise ValueError(errmsg("Invalid control character %r at", s, end)) 108 | else: 109 | _append(terminator) 110 | continue 111 | try: 112 | esc = s[end] 113 | except IndexError: 114 | raise ValueError( 115 | errmsg("Unterminated string starting at", s, begin)) 116 | if esc != 'u': 117 | try: 118 | m = _b[esc] 119 | except KeyError: 120 | raise ValueError( 121 | errmsg("Invalid \\escape: %r" % (esc,), s, end)) 122 | end += 1 123 | else: 124 | esc = s[end + 1:end + 5] 125 | next_end = end + 5 126 | msg = "Invalid \\uXXXX escape" 127 | try: 128 | if len(esc) != 4: 129 | raise ValueError 130 | uni = int(esc, 16) 131 | if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: 132 | msg = "Invalid \\uXXXX\\uXXXX surrogate pair" 133 | if not s[end + 5:end + 7] == '\\u': 134 | raise ValueError 135 | esc2 = s[end + 7:end + 11] 136 | if len(esc2) != 4: 137 | raise ValueError 138 | uni2 = int(esc2, 16) 139 | uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) 140 | next_end += 6 141 | m = unichr(uni) 142 | except ValueError: 143 | raise ValueError(errmsg(msg, s, end)) 144 | end = next_end 145 | _append(m) 146 | return u''.join(chunks), end 147 | 148 | 149 | # Use speedup 150 | try: 151 | scanstring = c_scanstring 152 | except NameError: 153 | scanstring = py_scanstring 154 | 155 | def JSONString(match, context): 156 | encoding = getattr(context, 'encoding', None) 157 | strict = getattr(context, 'strict', True) 158 | return scanstring(match.string, match.end(), encoding, strict) 159 | pattern(r'"')(JSONString) 160 | 161 | 162 | WHITESPACE = re.compile(r'\s*', FLAGS) 163 | 164 | def JSONObject(match, context, _w=WHITESPACE.match): 165 | pairs = {} 166 | s = match.string 167 | end = _w(s, match.end()).end() 168 | nextchar = s[end:end + 1] 169 | # Trivial empty object 170 | if nextchar == '}': 171 | return pairs, end + 1 172 | if nextchar != '"': 173 | raise ValueError(errmsg("Expecting property name", s, end)) 174 | end += 1 175 | encoding = getattr(context, 'encoding', None) 176 | strict = getattr(context, 'strict', True) 177 | iterscan = JSONScanner.iterscan 178 | while True: 179 | key, end = scanstring(s, end, encoding, strict) 180 | end = _w(s, end).end() 181 | if s[end:end + 1] != ':': 182 | raise ValueError(errmsg("Expecting : delimiter", s, end)) 183 | end = _w(s, end + 1).end() 184 | try: 185 | value, end = iterscan(s, idx=end, context=context).next() 186 | except StopIteration: 187 | raise ValueError(errmsg("Expecting object", s, end)) 188 | pairs[key] = value 189 | end = _w(s, end).end() 190 | nextchar = s[end:end + 1] 191 | end += 1 192 | if nextchar == '}': 193 | break 194 | if nextchar != ',': 195 | raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) 196 | end = _w(s, end).end() 197 | nextchar = s[end:end + 1] 198 | end += 1 199 | if nextchar != '"': 200 | raise ValueError(errmsg("Expecting property name", s, end - 1)) 201 | object_hook = getattr(context, 'object_hook', None) 202 | if object_hook is not None: 203 | pairs = object_hook(pairs) 204 | return pairs, end 205 | pattern(r'{')(JSONObject) 206 | 207 | 208 | def JSONArray(match, context, _w=WHITESPACE.match): 209 | values = [] 210 | s = match.string 211 | end = _w(s, match.end()).end() 212 | # Look-ahead for trivial empty array 213 | nextchar = s[end:end + 1] 214 | if nextchar == ']': 215 | return values, end + 1 216 | iterscan = JSONScanner.iterscan 217 | while True: 218 | try: 219 | value, end = iterscan(s, idx=end, context=context).next() 220 | except StopIteration: 221 | raise ValueError(errmsg("Expecting object", s, end)) 222 | values.append(value) 223 | end = _w(s, end).end() 224 | nextchar = s[end:end + 1] 225 | end += 1 226 | if nextchar == ']': 227 | break 228 | if nextchar != ',': 229 | raise ValueError(errmsg("Expecting , delimiter", s, end)) 230 | end = _w(s, end).end() 231 | return values, end 232 | pattern(r'\[')(JSONArray) 233 | 234 | 235 | ANYTHING = [ 236 | JSONObject, 237 | JSONArray, 238 | JSONString, 239 | JSONConstant, 240 | JSONNumber, 241 | ] 242 | 243 | JSONScanner = Scanner(ANYTHING) 244 | 245 | 246 | class JSONDecoder(object): 247 | """ 248 | Simple JSON decoder 249 | 250 | Performs the following translations in decoding by default: 251 | 252 | +---------------+-------------------+ 253 | | JSON | Python | 254 | +===============+===================+ 255 | | object | dict | 256 | +---------------+-------------------+ 257 | | array | list | 258 | +---------------+-------------------+ 259 | | string | unicode | 260 | +---------------+-------------------+ 261 | | number (int) | int, long | 262 | +---------------+-------------------+ 263 | | number (real) | float | 264 | +---------------+-------------------+ 265 | | true | True | 266 | +---------------+-------------------+ 267 | | false | False | 268 | +---------------+-------------------+ 269 | | null | None | 270 | +---------------+-------------------+ 271 | 272 | It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as 273 | their corresponding ``float`` values, which is outside the JSON spec. 274 | """ 275 | 276 | _scanner = Scanner(ANYTHING) 277 | __all__ = ['__init__', 'decode', 'raw_decode'] 278 | 279 | def __init__(self, encoding=None, object_hook=None, parse_float=None, 280 | parse_int=None, parse_constant=None, strict=True): 281 | """ 282 | ``encoding`` determines the encoding used to interpret any ``str`` 283 | objects decoded by this instance (utf-8 by default). It has no 284 | effect when decoding ``unicode`` objects. 285 | 286 | Note that currently only encodings that are a superset of ASCII work, 287 | strings of other encodings should be passed in as ``unicode``. 288 | 289 | ``object_hook``, if specified, will be called with the result 290 | of every JSON object decoded and its return value will be used in 291 | place of the given ``dict``. This can be used to provide custom 292 | deserializations (e.g. to support JSON-RPC class hinting). 293 | 294 | ``parse_float``, if specified, will be called with the string 295 | of every JSON float to be decoded. By default this is equivalent to 296 | float(num_str). This can be used to use another datatype or parser 297 | for JSON floats (e.g. decimal.Decimal). 298 | 299 | ``parse_int``, if specified, will be called with the string 300 | of every JSON int to be decoded. By default this is equivalent to 301 | int(num_str). This can be used to use another datatype or parser 302 | for JSON integers (e.g. float). 303 | 304 | ``parse_constant``, if specified, will be called with one of the 305 | following strings: -Infinity, Infinity, NaN, null, true, false. 306 | This can be used to raise an exception if invalid JSON numbers 307 | are encountered. 308 | """ 309 | self.encoding = encoding 310 | self.object_hook = object_hook 311 | self.parse_float = parse_float 312 | self.parse_int = parse_int 313 | self.parse_constant = parse_constant 314 | self.strict = strict 315 | 316 | def decode(self, s, _w=WHITESPACE.match): 317 | """ 318 | Return the Python representation of ``s`` (a ``str`` or ``unicode`` 319 | instance containing a JSON document) 320 | """ 321 | obj, end = self.raw_decode(s, idx=_w(s, 0).end()) 322 | end = _w(s, end).end() 323 | if end != len(s): 324 | raise ValueError(errmsg("Extra data", s, end, len(s))) 325 | return obj 326 | 327 | def raw_decode(self, s, **kw): 328 | """ 329 | Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning 330 | with a JSON document) and return a 2-tuple of the Python 331 | representation and the index in ``s`` where the document ended. 332 | 333 | This can be used to decode a JSON document from a string that may 334 | have extraneous data at the end. 335 | """ 336 | kw.setdefault('context', self) 337 | try: 338 | obj, end = self._scanner.iterscan(s, **kw).next() 339 | except StopIteration: 340 | raise ValueError("No JSON object could be decoded") 341 | return obj, end 342 | 343 | __all__ = ['JSONDecoder'] 344 | -------------------------------------------------------------------------------- /appspot/simplejson/encoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of JSONEncoder 3 | """ 4 | import re 5 | 6 | try: 7 | from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii 8 | except ImportError: 9 | pass 10 | 11 | ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') 12 | ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') 13 | HAS_UTF8 = re.compile(r'[\x80-\xff]') 14 | ESCAPE_DCT = { 15 | '\\': '\\\\', 16 | '"': '\\"', 17 | '\b': '\\b', 18 | '\f': '\\f', 19 | '\n': '\\n', 20 | '\r': '\\r', 21 | '\t': '\\t', 22 | } 23 | for i in range(0x20): 24 | ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) 25 | 26 | # Assume this produces an infinity on all machines (probably not guaranteed) 27 | INFINITY = float('1e66666') 28 | FLOAT_REPR = repr 29 | 30 | def floatstr(o, allow_nan=True): 31 | # Check for specials. Note that this type of test is processor- and/or 32 | # platform-specific, so do tests which don't depend on the internals. 33 | 34 | if o != o: 35 | text = 'NaN' 36 | elif o == INFINITY: 37 | text = 'Infinity' 38 | elif o == -INFINITY: 39 | text = '-Infinity' 40 | else: 41 | return FLOAT_REPR(o) 42 | 43 | if not allow_nan: 44 | raise ValueError("Out of range float values are not JSON compliant: %r" 45 | % (o,)) 46 | 47 | return text 48 | 49 | 50 | def encode_basestring(s): 51 | """ 52 | Return a JSON representation of a Python string 53 | """ 54 | def replace(match): 55 | return ESCAPE_DCT[match.group(0)] 56 | return '"' + ESCAPE.sub(replace, s) + '"' 57 | 58 | 59 | def py_encode_basestring_ascii(s): 60 | if isinstance(s, str) and HAS_UTF8.search(s) is not None: 61 | s = s.decode('utf-8') 62 | def replace(match): 63 | s = match.group(0) 64 | try: 65 | return ESCAPE_DCT[s] 66 | except KeyError: 67 | n = ord(s) 68 | if n < 0x10000: 69 | return '\\u%04x' % (n,) 70 | else: 71 | # surrogate pair 72 | n -= 0x10000 73 | s1 = 0xd800 | ((n >> 10) & 0x3ff) 74 | s2 = 0xdc00 | (n & 0x3ff) 75 | return '\\u%04x\\u%04x' % (s1, s2) 76 | return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' 77 | 78 | 79 | try: 80 | encode_basestring_ascii = c_encode_basestring_ascii 81 | except NameError: 82 | encode_basestring_ascii = py_encode_basestring_ascii 83 | 84 | 85 | class JSONEncoder(object): 86 | """ 87 | Extensible JSON encoder for Python data structures. 88 | 89 | Supports the following objects and types by default: 90 | 91 | +-------------------+---------------+ 92 | | Python | JSON | 93 | +===================+===============+ 94 | | dict | object | 95 | +-------------------+---------------+ 96 | | list, tuple | array | 97 | +-------------------+---------------+ 98 | | str, unicode | string | 99 | +-------------------+---------------+ 100 | | int, long, float | number | 101 | +-------------------+---------------+ 102 | | True | true | 103 | +-------------------+---------------+ 104 | | False | false | 105 | +-------------------+---------------+ 106 | | None | null | 107 | +-------------------+---------------+ 108 | 109 | To extend this to recognize other objects, subclass and implement a 110 | ``.default()`` method with another method that returns a serializable 111 | object for ``o`` if possible, otherwise it should call the superclass 112 | implementation (to raise ``TypeError``). 113 | """ 114 | __all__ = ['__init__', 'default', 'encode', 'iterencode'] 115 | item_separator = ', ' 116 | key_separator = ': ' 117 | def __init__(self, skipkeys=False, ensure_ascii=True, 118 | check_circular=True, allow_nan=True, sort_keys=False, 119 | indent=None, separators=None, encoding='utf-8', default=None): 120 | """ 121 | Constructor for JSONEncoder, with sensible defaults. 122 | 123 | If skipkeys is False, then it is a TypeError to attempt 124 | encoding of keys that are not str, int, long, float or None. If 125 | skipkeys is True, such items are simply skipped. 126 | 127 | If ensure_ascii is True, the output is guaranteed to be str 128 | objects with all incoming unicode characters escaped. If 129 | ensure_ascii is false, the output will be unicode object. 130 | 131 | If check_circular is True, then lists, dicts, and custom encoded 132 | objects will be checked for circular references during encoding to 133 | prevent an infinite recursion (which would cause an OverflowError). 134 | Otherwise, no such check takes place. 135 | 136 | If allow_nan is True, then NaN, Infinity, and -Infinity will be 137 | encoded as such. This behavior is not JSON specification compliant, 138 | but is consistent with most JavaScript based encoders and decoders. 139 | Otherwise, it will be a ValueError to encode such floats. 140 | 141 | If sort_keys is True, then the output of dictionaries will be 142 | sorted by key; this is useful for regression tests to ensure 143 | that JSON serializations can be compared on a day-to-day basis. 144 | 145 | If indent is a non-negative integer, then JSON array 146 | elements and object members will be pretty-printed with that 147 | indent level. An indent level of 0 will only insert newlines. 148 | None is the most compact representation. 149 | 150 | If specified, separators should be a (item_separator, key_separator) 151 | tuple. The default is (', ', ': '). To get the most compact JSON 152 | representation you should specify (',', ':') to eliminate whitespace. 153 | 154 | If specified, default is a function that gets called for objects 155 | that can't otherwise be serialized. It should return a JSON encodable 156 | version of the object or raise a ``TypeError``. 157 | 158 | If encoding is not None, then all input strings will be 159 | transformed into unicode using that encoding prior to JSON-encoding. 160 | The default is UTF-8. 161 | """ 162 | 163 | self.skipkeys = skipkeys 164 | self.ensure_ascii = ensure_ascii 165 | self.check_circular = check_circular 166 | self.allow_nan = allow_nan 167 | self.sort_keys = sort_keys 168 | self.indent = indent 169 | self.current_indent_level = 0 170 | if separators is not None: 171 | self.item_separator, self.key_separator = separators 172 | if default is not None: 173 | self.default = default 174 | self.encoding = encoding 175 | 176 | def _newline_indent(self): 177 | return '\n' + (' ' * (self.indent * self.current_indent_level)) 178 | 179 | def _iterencode_list(self, lst, markers=None): 180 | if not lst: 181 | yield '[]' 182 | return 183 | if markers is not None: 184 | markerid = id(lst) 185 | if markerid in markers: 186 | raise ValueError("Circular reference detected") 187 | markers[markerid] = lst 188 | yield '[' 189 | if self.indent is not None: 190 | self.current_indent_level += 1 191 | newline_indent = self._newline_indent() 192 | separator = self.item_separator + newline_indent 193 | yield newline_indent 194 | else: 195 | newline_indent = None 196 | separator = self.item_separator 197 | first = True 198 | for value in lst: 199 | if first: 200 | first = False 201 | else: 202 | yield separator 203 | for chunk in self._iterencode(value, markers): 204 | yield chunk 205 | if newline_indent is not None: 206 | self.current_indent_level -= 1 207 | yield self._newline_indent() 208 | yield ']' 209 | if markers is not None: 210 | del markers[markerid] 211 | 212 | def _iterencode_dict(self, dct, markers=None): 213 | if not dct: 214 | yield '{}' 215 | return 216 | if markers is not None: 217 | markerid = id(dct) 218 | if markerid in markers: 219 | raise ValueError("Circular reference detected") 220 | markers[markerid] = dct 221 | yield '{' 222 | key_separator = self.key_separator 223 | if self.indent is not None: 224 | self.current_indent_level += 1 225 | newline_indent = self._newline_indent() 226 | item_separator = self.item_separator + newline_indent 227 | yield newline_indent 228 | else: 229 | newline_indent = None 230 | item_separator = self.item_separator 231 | first = True 232 | if self.ensure_ascii: 233 | encoder = encode_basestring_ascii 234 | else: 235 | encoder = encode_basestring 236 | allow_nan = self.allow_nan 237 | if self.sort_keys: 238 | keys = dct.keys() 239 | keys.sort() 240 | items = [(k, dct[k]) for k in keys] 241 | else: 242 | items = dct.iteritems() 243 | _encoding = self.encoding 244 | _do_decode = (_encoding is not None 245 | and not (_encoding == 'utf-8')) 246 | for key, value in items: 247 | if isinstance(key, str): 248 | if _do_decode: 249 | key = key.decode(_encoding) 250 | elif isinstance(key, basestring): 251 | pass 252 | # JavaScript is weakly typed for these, so it makes sense to 253 | # also allow them. Many encoders seem to do something like this. 254 | elif isinstance(key, float): 255 | key = floatstr(key, allow_nan) 256 | elif isinstance(key, (int, long)): 257 | key = str(key) 258 | elif key is True: 259 | key = 'true' 260 | elif key is False: 261 | key = 'false' 262 | elif key is None: 263 | key = 'null' 264 | elif self.skipkeys: 265 | continue 266 | else: 267 | raise TypeError("key %r is not a string" % (key,)) 268 | if first: 269 | first = False 270 | else: 271 | yield item_separator 272 | yield encoder(key) 273 | yield key_separator 274 | for chunk in self._iterencode(value, markers): 275 | yield chunk 276 | if newline_indent is not None: 277 | self.current_indent_level -= 1 278 | yield self._newline_indent() 279 | yield '}' 280 | if markers is not None: 281 | del markers[markerid] 282 | 283 | def _iterencode(self, o, markers=None): 284 | if isinstance(o, basestring): 285 | if self.ensure_ascii: 286 | encoder = encode_basestring_ascii 287 | else: 288 | encoder = encode_basestring 289 | _encoding = self.encoding 290 | if (_encoding is not None and isinstance(o, str) 291 | and not (_encoding == 'utf-8')): 292 | o = o.decode(_encoding) 293 | yield encoder(o) 294 | elif o is None: 295 | yield 'null' 296 | elif o is True: 297 | yield 'true' 298 | elif o is False: 299 | yield 'false' 300 | elif isinstance(o, (int, long)): 301 | yield str(o) 302 | elif isinstance(o, float): 303 | yield floatstr(o, self.allow_nan) 304 | elif isinstance(o, (list, tuple)): 305 | for chunk in self._iterencode_list(o, markers): 306 | yield chunk 307 | elif isinstance(o, dict): 308 | for chunk in self._iterencode_dict(o, markers): 309 | yield chunk 310 | else: 311 | if markers is not None: 312 | markerid = id(o) 313 | if markerid in markers: 314 | raise ValueError("Circular reference detected") 315 | markers[markerid] = o 316 | for chunk in self._iterencode_default(o, markers): 317 | yield chunk 318 | if markers is not None: 319 | del markers[markerid] 320 | 321 | def _iterencode_default(self, o, markers=None): 322 | newobj = self.default(o) 323 | return self._iterencode(newobj, markers) 324 | 325 | def default(self, o): 326 | """ 327 | Implement this method in a subclass such that it returns 328 | a serializable object for ``o``, or calls the base implementation 329 | (to raise a ``TypeError``). 330 | 331 | For example, to support arbitrary iterators, you could 332 | implement default like this:: 333 | 334 | def default(self, o): 335 | try: 336 | iterable = iter(o) 337 | except TypeError: 338 | pass 339 | else: 340 | return list(iterable) 341 | return JSONEncoder.default(self, o) 342 | """ 343 | raise TypeError("%r is not JSON serializable" % (o,)) 344 | 345 | def encode(self, o): 346 | """ 347 | Return a JSON string representation of a Python data structure. 348 | 349 | >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) 350 | '{"foo": ["bar", "baz"]}' 351 | """ 352 | # This is for extremely simple cases and benchmarks. 353 | if isinstance(o, basestring): 354 | if isinstance(o, str): 355 | _encoding = self.encoding 356 | if (_encoding is not None 357 | and not (_encoding == 'utf-8')): 358 | o = o.decode(_encoding) 359 | if self.ensure_ascii: 360 | return encode_basestring_ascii(o) 361 | else: 362 | return encode_basestring(o) 363 | # This doesn't pass the iterator directly to ''.join() because the 364 | # exceptions aren't as detailed. The list call should be roughly 365 | # equivalent to the PySequence_Fast that ''.join() would do. 366 | chunks = list(self.iterencode(o)) 367 | return ''.join(chunks) 368 | 369 | def iterencode(self, o): 370 | """ 371 | Encode the given object and yield each string 372 | representation as available. 373 | 374 | For example:: 375 | 376 | for chunk in JSONEncoder().iterencode(bigobject): 377 | mysocket.write(chunk) 378 | """ 379 | if self.check_circular: 380 | markers = {} 381 | else: 382 | markers = None 383 | return self._iterencode(o, markers) 384 | 385 | __all__ = ['JSONEncoder'] 386 | -------------------------------------------------------------------------------- /appspot/simplejson/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | A simple, fast, extensible JSON encoder and decoder 3 | 4 | JSON (JavaScript Object Notation) is a subset of 5 | JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data 6 | interchange format. 7 | 8 | simplejson exposes an API familiar to uses of the standard library 9 | marshal and pickle modules. 10 | 11 | Encoding basic Python object hierarchies:: 12 | 13 | >>> import simplejson 14 | >>> simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) 15 | '["foo", {"bar": ["baz", null, 1.0, 2]}]' 16 | >>> print simplejson.dumps("\"foo\bar") 17 | "\"foo\bar" 18 | >>> print simplejson.dumps(u'\u1234') 19 | "\u1234" 20 | >>> print simplejson.dumps('\\') 21 | "\\" 22 | >>> print simplejson.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) 23 | {"a": 0, "b": 0, "c": 0} 24 | >>> from StringIO import StringIO 25 | >>> io = StringIO() 26 | >>> simplejson.dump(['streaming API'], io) 27 | >>> io.getvalue() 28 | '["streaming API"]' 29 | 30 | Compact encoding:: 31 | 32 | >>> import simplejson 33 | >>> simplejson.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) 34 | '[1,2,3,{"4":5,"6":7}]' 35 | 36 | Pretty printing:: 37 | 38 | >>> import simplejson 39 | >>> print simplejson.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) 40 | { 41 | "4": 5, 42 | "6": 7 43 | } 44 | 45 | Decoding JSON:: 46 | 47 | >>> import simplejson 48 | >>> simplejson.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') 49 | [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] 50 | >>> simplejson.loads('"\\"foo\\bar"') 51 | u'"foo\x08ar' 52 | >>> from StringIO import StringIO 53 | >>> io = StringIO('["streaming API"]') 54 | >>> simplejson.load(io) 55 | [u'streaming API'] 56 | 57 | Specializing JSON object decoding:: 58 | 59 | >>> import simplejson 60 | >>> def as_complex(dct): 61 | ... if '__complex__' in dct: 62 | ... return complex(dct['real'], dct['imag']) 63 | ... return dct 64 | ... 65 | >>> simplejson.loads('{"__complex__": true, "real": 1, "imag": 2}', 66 | ... object_hook=as_complex) 67 | (1+2j) 68 | >>> import decimal 69 | >>> simplejson.loads('1.1', parse_float=decimal.Decimal) 70 | Decimal("1.1") 71 | 72 | Extending JSONEncoder:: 73 | 74 | >>> import simplejson 75 | >>> class ComplexEncoder(simplejson.JSONEncoder): 76 | ... def default(self, obj): 77 | ... if isinstance(obj, complex): 78 | ... return [obj.real, obj.imag] 79 | ... return simplejson.JSONEncoder.default(self, obj) 80 | ... 81 | >>> dumps(2 + 1j, cls=ComplexEncoder) 82 | '[2.0, 1.0]' 83 | >>> ComplexEncoder().encode(2 + 1j) 84 | '[2.0, 1.0]' 85 | >>> list(ComplexEncoder().iterencode(2 + 1j)) 86 | ['[', '2.0', ', ', '1.0', ']'] 87 | 88 | 89 | Using simplejson from the shell to validate and 90 | pretty-print:: 91 | 92 | $ echo '{"json":"obj"}' | python -msimplejson.tool 93 | { 94 | "json": "obj" 95 | } 96 | $ echo '{ 1.2:3.4}' | python -msimplejson.tool 97 | Expecting property name: line 1 column 2 (char 2) 98 | 99 | Note that the JSON produced by this module's default settings 100 | is a subset of YAML, so it may be used as a serializer for that as well. 101 | """ 102 | __version__ = '1.9.2' 103 | __all__ = [ 104 | 'dump', 'dumps', 'load', 'loads', 105 | 'JSONDecoder', 'JSONEncoder', 106 | ] 107 | 108 | if __name__ == '__main__': 109 | import warnings 110 | warnings.warn('python -msimplejson is deprecated, use python -msiplejson.tool', DeprecationWarning) 111 | from simplejson.decoder import JSONDecoder 112 | from simplejson.encoder import JSONEncoder 113 | else: 114 | from decoder import JSONDecoder 115 | from encoder import JSONEncoder 116 | 117 | _default_encoder = JSONEncoder( 118 | skipkeys=False, 119 | ensure_ascii=True, 120 | check_circular=True, 121 | allow_nan=True, 122 | indent=None, 123 | separators=None, 124 | encoding='utf-8', 125 | default=None, 126 | ) 127 | 128 | def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, 129 | allow_nan=True, cls=None, indent=None, separators=None, 130 | encoding='utf-8', default=None, **kw): 131 | """ 132 | Serialize ``obj`` as a JSON formatted stream to ``fp`` (a 133 | ``.write()``-supporting file-like object). 134 | 135 | If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types 136 | (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) 137 | will be skipped instead of raising a ``TypeError``. 138 | 139 | If ``ensure_ascii`` is ``False``, then the some chunks written to ``fp`` 140 | may be ``unicode`` instances, subject to normal Python ``str`` to 141 | ``unicode`` coercion rules. Unless ``fp.write()`` explicitly 142 | understands ``unicode`` (as in ``codecs.getwriter()``) this is likely 143 | to cause an error. 144 | 145 | If ``check_circular`` is ``False``, then the circular reference check 146 | for container types will be skipped and a circular reference will 147 | result in an ``OverflowError`` (or worse). 148 | 149 | If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to 150 | serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) 151 | in strict compliance of the JSON specification, instead of using the 152 | JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). 153 | 154 | If ``indent`` is a non-negative integer, then JSON array elements and object 155 | members will be pretty-printed with that indent level. An indent level 156 | of 0 will only insert newlines. ``None`` is the most compact representation. 157 | 158 | If ``separators`` is an ``(item_separator, dict_separator)`` tuple 159 | then it will be used instead of the default ``(', ', ': ')`` separators. 160 | ``(',', ':')`` is the most compact JSON representation. 161 | 162 | ``encoding`` is the character encoding for str instances, default is UTF-8. 163 | 164 | ``default(obj)`` is a function that should return a serializable version 165 | of obj or raise TypeError. The default simply raises TypeError. 166 | 167 | To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the 168 | ``.default()`` method to serialize additional types), specify it with 169 | the ``cls`` kwarg. 170 | """ 171 | # cached encoder 172 | if (skipkeys is False and ensure_ascii is True and 173 | check_circular is True and allow_nan is True and 174 | cls is None and indent is None and separators is None and 175 | encoding == 'utf-8' and default is None and not kw): 176 | iterable = _default_encoder.iterencode(obj) 177 | else: 178 | if cls is None: 179 | cls = JSONEncoder 180 | iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, 181 | check_circular=check_circular, allow_nan=allow_nan, indent=indent, 182 | separators=separators, encoding=encoding, 183 | default=default, **kw).iterencode(obj) 184 | # could accelerate with writelines in some versions of Python, at 185 | # a debuggability cost 186 | for chunk in iterable: 187 | fp.write(chunk) 188 | 189 | 190 | def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, 191 | allow_nan=True, cls=None, indent=None, separators=None, 192 | encoding='utf-8', default=None, **kw): 193 | """ 194 | Serialize ``obj`` to a JSON formatted ``str``. 195 | 196 | If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types 197 | (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) 198 | will be skipped instead of raising a ``TypeError``. 199 | 200 | If ``ensure_ascii`` is ``False``, then the return value will be a 201 | ``unicode`` instance subject to normal Python ``str`` to ``unicode`` 202 | coercion rules instead of being escaped to an ASCII ``str``. 203 | 204 | If ``check_circular`` is ``False``, then the circular reference check 205 | for container types will be skipped and a circular reference will 206 | result in an ``OverflowError`` (or worse). 207 | 208 | If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to 209 | serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in 210 | strict compliance of the JSON specification, instead of using the 211 | JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). 212 | 213 | If ``indent`` is a non-negative integer, then JSON array elements and 214 | object members will be pretty-printed with that indent level. An indent 215 | level of 0 will only insert newlines. ``None`` is the most compact 216 | representation. 217 | 218 | If ``separators`` is an ``(item_separator, dict_separator)`` tuple 219 | then it will be used instead of the default ``(', ', ': ')`` separators. 220 | ``(',', ':')`` is the most compact JSON representation. 221 | 222 | ``encoding`` is the character encoding for str instances, default is UTF-8. 223 | 224 | ``default(obj)`` is a function that should return a serializable version 225 | of obj or raise TypeError. The default simply raises TypeError. 226 | 227 | To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the 228 | ``.default()`` method to serialize additional types), specify it with 229 | the ``cls`` kwarg. 230 | """ 231 | # cached encoder 232 | if (skipkeys is False and ensure_ascii is True and 233 | check_circular is True and allow_nan is True and 234 | cls is None and indent is None and separators is None and 235 | encoding == 'utf-8' and default is None and not kw): 236 | return _default_encoder.encode(obj) 237 | if cls is None: 238 | cls = JSONEncoder 239 | return cls( 240 | skipkeys=skipkeys, ensure_ascii=ensure_ascii, 241 | check_circular=check_circular, allow_nan=allow_nan, indent=indent, 242 | separators=separators, encoding=encoding, default=default, 243 | **kw).encode(obj) 244 | 245 | 246 | _default_decoder = JSONDecoder(encoding=None, object_hook=None) 247 | 248 | 249 | def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, 250 | parse_int=None, parse_constant=None, **kw): 251 | """ 252 | Deserialize ``fp`` (a ``.read()``-supporting file-like object containing 253 | a JSON document) to a Python object. 254 | 255 | If the contents of ``fp`` is encoded with an ASCII based encoding other 256 | than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must 257 | be specified. Encodings that are not ASCII based (such as UCS-2) are 258 | not allowed, and should be wrapped with 259 | ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode`` 260 | object and passed to ``loads()`` 261 | 262 | ``object_hook`` is an optional function that will be called with the 263 | result of any object literal decode (a ``dict``). The return value of 264 | ``object_hook`` will be used instead of the ``dict``. This feature 265 | can be used to implement custom decoders (e.g. JSON-RPC class hinting). 266 | 267 | To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` 268 | kwarg. 269 | """ 270 | return loads(fp.read(), 271 | encoding=encoding, cls=cls, object_hook=object_hook, 272 | parse_float=parse_float, parse_int=parse_int, 273 | parse_constant=parse_constant, **kw) 274 | 275 | 276 | def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, 277 | parse_int=None, parse_constant=None, **kw): 278 | """ 279 | Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON 280 | document) to a Python object. 281 | 282 | If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding 283 | other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name 284 | must be specified. Encodings that are not ASCII based (such as UCS-2) 285 | are not allowed and should be decoded to ``unicode`` first. 286 | 287 | ``object_hook`` is an optional function that will be called with the 288 | result of any object literal decode (a ``dict``). The return value of 289 | ``object_hook`` will be used instead of the ``dict``. This feature 290 | can be used to implement custom decoders (e.g. JSON-RPC class hinting). 291 | 292 | ``parse_float``, if specified, will be called with the string 293 | of every JSON float to be decoded. By default this is equivalent to 294 | float(num_str). This can be used to use another datatype or parser 295 | for JSON floats (e.g. decimal.Decimal). 296 | 297 | ``parse_int``, if specified, will be called with the string 298 | of every JSON int to be decoded. By default this is equivalent to 299 | int(num_str). This can be used to use another datatype or parser 300 | for JSON integers (e.g. float). 301 | 302 | ``parse_constant``, if specified, will be called with one of the 303 | following strings: -Infinity, Infinity, NaN, null, true, false. 304 | This can be used to raise an exception if invalid JSON numbers 305 | are encountered. 306 | 307 | To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` 308 | kwarg. 309 | """ 310 | if (cls is None and encoding is None and object_hook is None and 311 | parse_int is None and parse_float is None and 312 | parse_constant is None and not kw): 313 | return _default_decoder.decode(s) 314 | if cls is None: 315 | cls = JSONDecoder 316 | if object_hook is not None: 317 | kw['object_hook'] = object_hook 318 | if parse_float is not None: 319 | kw['parse_float'] = parse_float 320 | if parse_int is not None: 321 | kw['parse_int'] = parse_int 322 | if parse_constant is not None: 323 | kw['parse_constant'] = parse_constant 324 | return cls(encoding=encoding, **kw).decode(s) 325 | 326 | 327 | # 328 | # Compatibility cruft from other libraries 329 | # 330 | 331 | 332 | def decode(s): 333 | """ 334 | demjson, python-cjson API compatibility hook. Use loads(s) instead. 335 | """ 336 | import warnings 337 | warnings.warn("simplejson.loads(s) should be used instead of decode(s)", 338 | DeprecationWarning) 339 | return loads(s) 340 | 341 | 342 | def encode(obj): 343 | """ 344 | demjson, python-cjson compatibility hook. Use dumps(s) instead. 345 | """ 346 | import warnings 347 | warnings.warn("simplejson.dumps(s) should be used instead of encode(s)", 348 | DeprecationWarning) 349 | return dumps(obj) 350 | 351 | 352 | def read(s): 353 | """ 354 | jsonlib, JsonUtils, python-json, json-py API compatibility hook. 355 | Use loads(s) instead. 356 | """ 357 | import warnings 358 | warnings.warn("simplejson.loads(s) should be used instead of read(s)", 359 | DeprecationWarning) 360 | return loads(s) 361 | 362 | 363 | def write(obj): 364 | """ 365 | jsonlib, JsonUtils, python-json, json-py API compatibility hook. 366 | Use dumps(s) instead. 367 | """ 368 | import warnings 369 | warnings.warn("simplejson.dumps(s) should be used instead of write(s)", 370 | DeprecationWarning) 371 | return dumps(obj) 372 | 373 | 374 | if __name__ == '__main__': 375 | import simplejson.tool 376 | simplejson.tool.main() 377 | -------------------------------------------------------------------------------- /appspot/restlet/intgutil.py: -------------------------------------------------------------------------------- 1 | ''' Json-Rest-handlin' integration helper. 2 | 3 | This module offers a JSON+REST-handling integration class meant to be used with 4 | Google App Engine (hooked into a webapp.RequestHandler subclass); it can be 5 | hooked up by simply passing an object h with attributes h.request and 6 | h.response that are duck-like those of webapp.RequestHandler. 7 | 8 | On hookup, the integration-helper class overrides the get/set/put/delete 9 | methods of the object hooking up to it so that they respond appropriately to 10 | REST requests (as documented in json_rest.txt) based on registrations performed 11 | in restutil, parsing and formatting JSON payloads based on jsonutil. 12 | 13 | IOW, this helper integrates functionality found in other modules of the 14 | gae-json-rest package: 15 | parsutil 16 | restutil 17 | jsonutil 18 | "putting it all together" into a highly-reusable (but still modestly 19 | customizable) REST-style, JSON-transport server web-app for GAE. 20 | 21 | TODO: decide what arguments/parameters are passed to various kinds of 22 | methods being called, and implement that decision; add MANY tests!!! 23 | ''' 24 | import logging 25 | 26 | import jsonutil 27 | import parsutil 28 | import restutil 29 | import auth 30 | 31 | class JsonRestHelper(object): 32 | 33 | prefix_to_ignore = '/' 34 | #prefix_to_ignore = '/rest' 35 | __delete_parser = __put_parser = __post_parser = __get_parser = None 36 | 37 | def hookup(self, handler): 38 | """ "Hooks up" this helper instance to a handler object. 39 | 40 | Args: 41 | handler: an instance of a webapp.RequestHandler subclass 42 | Side effects: 43 | - sets self.handler to handler 44 | - sets the handler's get, put, post and delete methods from self 45 | - sets the handler's jrh attribute to self 46 | Note this creates reference loops and MUST be undone in hookdown! 47 | """ 48 | logging.info('hookup %r/%r', self, handler) 49 | self.handler = handler 50 | handler.get = self.get 51 | handler.put = self.put 52 | handler.post = self.post 53 | handler.delete = self.delete 54 | handler.jrh = self 55 | 56 | def is_authorized(self): 57 | return auth.is_authorized(self.handler.request) 58 | 59 | def hookdown(self): 60 | """ Undoes the effects of self.hookup """ 61 | logging.info('hookdn %r/%r', self, self.handler) 62 | h = self.handler 63 | h.jrh = self.handler = None 64 | del h.get, h.put, h.post, h.delete 65 | 66 | def _serve(self, data): 67 | """ Serves a result in JSON, and hooks-down from the handler """ 68 | try: return jsonutil.send_json(self.handler.response, data) 69 | finally: self.hookdown() 70 | 71 | def get_model(self, modelname): 72 | """ Gets a model (or None) given a model name. 73 | 74 | Args: 75 | modelname: a string that should name a model 76 | Returns: 77 | a model class, or None (if no model's registered with that name) 78 | Side effects: 79 | sets response status to 400 if no model's registered with that name 80 | """ 81 | model = restutil.modelClassFromName(modelname) 82 | if model is None: 83 | self.handler.response.set_status(400, 'Model %r not found' % modelname) 84 | return model 85 | 86 | def get_special(self, specialname): 87 | """ Gets a special (or None) given a special object's name. 88 | 89 | Args: 90 | specialname: a string that should name a special object 91 | Returns: 92 | a special object, or None (if no special's registered with that name) 93 | Side effects: 94 | sets response status to 400 if no special's registered with that name 95 | """ 96 | special = restutil.specialFromName(specialname) 97 | if special is None: 98 | self.handler.response.set_status(400, 'Special object %r not found' % 99 | specialname) 100 | return special 101 | 102 | def get_entity(self, modelname, strid): 103 | """ Gets an entity (or None) given a model name and entity ID as string. 104 | 105 | Args: 106 | modelname: a string that should name a model 107 | strid: the str(id) for the numeric id of an entity of that model 108 | Returns: 109 | an entity, or None (if something went wrong) 110 | Side effects: 111 | sets response status to 400 or 404 if various things went wrong 112 | """ 113 | model = self.get_model(modelname) 114 | if model is None: 115 | return None 116 | entity = model.get_by_id(int(strid)) 117 | if entity is None: 118 | self.handler.response.set_status(404, "Entity %s/%s not found" % 119 | (modelname, strid)) 120 | return entity 121 | 122 | def get_special_method(self, specialname, methodname): 123 | """ Gets a special object method (or None) given special & method names. 124 | 125 | Args: 126 | specialname: a string that should name a special object 127 | methodname: a string that should name a method of that special object 128 | Returns: 129 | the method with that name in the special object of that name 130 | Side effects: 131 | sets response status to 400 if special or method not found 132 | """ 133 | special = self.get_special(specialname) 134 | if special is None: return '' 135 | method = special.get(methodname) 136 | if method is None: 137 | self.handler.response.set_status(400, 'Method %r not found in special %r' 138 | % (methodname, specialname)) 139 | return method 140 | 141 | def _methodhelper(self, modelname, methodname, _getter): 142 | """ Gets a model or instance method given model and method names & getter. 143 | 144 | Args: 145 | modelname: a string that should name a model 146 | methodname: a string that should name a method of that model 147 | (model-method or instance-method, dep. on _getter) 148 | Returns: 149 | a method object, or None if either model or method were not found 150 | Side effects: 151 | sets response status to 400 if either model or method were not found 152 | """ 153 | model = self.get_model(modelname) 154 | if model is None: return '' 155 | method = _getter(model, methodname) 156 | if method is None: 157 | self.handler.response.set_status(400, 'Method %r not found in model' % 158 | (methodname, modelname)) 159 | return method 160 | 161 | def get_model_method(self, modelname, methodname): 162 | """ Gets a model's method given model and method names. 163 | 164 | Args: 165 | modelname: a string that should name a model 166 | methodname: a sring that should name a method of that model 167 | Returns: 168 | a method object, or None if either model or method were not found 169 | Side effects: 170 | sets response status to 400 if either model or method were not found 171 | """ 172 | return self._methodhelper(modelname, methodname, restutil.modelMethodByName) 173 | 174 | def get_instance_method(self, modelname, methodname): 175 | """ Gets an instance method given model and method names. 176 | 177 | Args: 178 | modelname: a string that should name a model 179 | methodname: a sring that should name an instance method of that model 180 | Returns: 181 | a method object, or None if either model or method were not found 182 | Side effects: 183 | sets response status to 400 if either model or method were not found 184 | """ 185 | return self._methodhelper(modelname, methodname, restutil.instanceMethodByName) 186 | 187 | 188 | def do_delete(self, model, strid): 189 | """ Hook method to delete an entity given modelname and strid. 190 | """ 191 | entity = self.get_entity(model, strid) 192 | if entity is not None: 193 | entity.delete() 194 | return {} 195 | 196 | def delete(self, prefix=None): 197 | """ Delete an entity given by path modelname/strid 198 | Response is JSON for an empty jobj. 199 | """ 200 | if not self.is_authorized(): 201 | self.handler.response.set_status(403, "You don't have permission to access on this server.") 202 | return self._serve({}) 203 | if self.__delete_parser is None: 204 | self.__delete_parser = parsutil.RestUrlParser(self.prefix_to_ignore, 205 | do_model_strid=self.do_delete) 206 | path = self.handler.request.path 207 | result = self.__delete_parser.process(path, prefix) 208 | if result is None or isinstance(result, tuple): 209 | self.handler.response.set_status(400, 'Invalid URL for DELETE: %r' % path) 210 | return self._serve(result) 211 | 212 | def do_put(self, model, strid): 213 | """ Hook method to update an entity given modelname and strid. 214 | """ 215 | entity = self.get_entity(model, strid) 216 | if entity is None: 217 | return {} 218 | jobj = jsonutil.receive_json(self.handler.request) 219 | jobj = jsonutil.update_entity(entity, jobj) 220 | updated_entity_path = "/%s/%s" % (model, jobj['id']) 221 | self.handler.response.set_status(200, 'Updated entity %s' % 222 | updated_entity_path) 223 | return jobj 224 | 225 | def put(self, prefix=None): 226 | """ Update an entity given by path modelname/strid 227 | Request body is JSON for the needed changes 228 | Response is JSON for the updated entity. 229 | """ 230 | if not self.is_authorized(): 231 | self.handler.response.set_status(403, "You don't have permission to access on this server.") 232 | return self._serve({}) 233 | if self.__put_parser is None: 234 | self.__put_parser = parsutil.RestUrlParser(self.prefix_to_ignore, 235 | do_model_strid=self.do_put) 236 | path = self.handler.request.path 237 | result = self.__put_parser.process(path, prefix) 238 | if result is None or isinstance(result, tuple): 239 | self.handler.response.set_status(400, 'Invalid URL for POST: %r' % path) 240 | return self._serve({}) 241 | return self._serve(result) 242 | 243 | def do_post_special_method(self, special, method): 244 | """ Hook method to call a method on a special object given names. 245 | """ 246 | themethod = self.get_special_method(special, method) 247 | if special is None: return '' 248 | try: return themethod() 249 | except Exception, e: 250 | self.handler.response.set_status(400, "Can't call %r/%r: %s" % ( 251 | special, method, e)) 252 | return '' 253 | 254 | def do_post_model(self, model): 255 | """ Hook method to "call a model" (to create an entity) 256 | """ 257 | themodel = self.get_model(model) 258 | if themodel is None: return '' 259 | jobj = jsonutil.receive_json(self.handler.request) 260 | jobj = jsonutil.make_entity(themodel, jobj) 261 | self._classname = model 262 | return jobj 263 | 264 | def do_post_model_method(self, model, method): 265 | """ Hook method to call a method on a model given s. 266 | """ 267 | themethod = self.get_model_method(model, method) 268 | if themethod is None: return '' 269 | try: return themethod() 270 | except Exception, e: 271 | self.handler.response.set_status(400, "Can't call %r/%r: %s" % ( 272 | model, method, e)) 273 | return '' 274 | 275 | def do_post_entity_method(self, model, strid, method): 276 | """ Hook method to call a method on an entity given s and strid. 277 | """ 278 | themethod = self.get_instance_method(model, method) 279 | if themethod is None: return '' 280 | entity = self.get_entity(model, strid) 281 | if entity is None: return '' 282 | try: return themethod(entity) 283 | except Exception, e: 284 | self.handler.response.set_status(400, "Can't call %r/%r/%r: %s" % ( 285 | model, strid, method, e)) 286 | return '' 287 | 288 | def post(self, prefix=None): 289 | """ Create an entity ("call a model") or perform other non-R/O "call". 290 | 291 | Request body is JSON for the needed entity or other call "args". 292 | Response is JSON for the updated entity (or "call result"). 293 | """ 294 | if not self.is_authorized(): 295 | self.handler.response.set_status(403, "You don't have permission to access on this server.") 296 | return self._serve({}) 297 | if self.__post_parser is None: 298 | self.__post_parser = parsutil.RestUrlParser(self.prefix_to_ignore, 299 | do_special_method=self.do_post_special_method, 300 | do_model=self.do_post_model, 301 | do_model_method=self.do_post_model_method, 302 | do_model_strid_method=self.do_post_entity_method, 303 | ) 304 | path = self.handler.request.path 305 | result = self.__post_parser.process(path, prefix) 306 | if result is None or isinstance(result, tuple): 307 | self.handler.response.set_status(400, 'Invalid URL for POST: %r' % path) 308 | return self._serve({}) 309 | try: 310 | strid = result['id'] 311 | except (KeyError, AttributeError, TypeError): 312 | pass 313 | else: 314 | new_entity_path = "/%s/%s" % (self._classname, strid) 315 | logging.info('Post (%r) created %r', path, new_entity_path) 316 | self.handler.response.headers['Location'] = new_entity_path 317 | self.handler.response.set_status(201, 'Created entity %s' % 318 | new_entity_path) 319 | return self._serve(result) 320 | 321 | def do_get_special_method(self, special, method): 322 | """ Hook method to R/O call a method on a special object given names. 323 | """ 324 | themethod = self.get_special_method(special, method) 325 | if themethod is None: return '' 326 | try: return themethod() 327 | except Exception, e: 328 | self.handler.response.set_status(400, "Can't call %r/%r: %s" % ( 329 | special, method, e)) 330 | return '' 331 | 332 | def do_get_model(self, model): 333 | """ Hook method to R/O "call a model" ("get list of all its IDs"...?) 334 | """ 335 | themodel = self.get_model(model) 336 | if themodel is None: return '' 337 | return [jsonutil.id_of(x) for x in themodel.all()] 338 | 339 | def do_get_entity(self, model, strid): 340 | """ Hook method to get data about an entity given model name and strid 341 | """ 342 | entity = self.get_entity(model, strid) 343 | if entity is None: 344 | return {} 345 | return jsonutil.make_jobj(entity) 346 | 347 | def do_get_model_method(self, model, method): 348 | """ Hook method to R/O call a method on a model given s. 349 | """ 350 | themethod = self.get_model_method(model, method) 351 | if themethod is None: return '' 352 | try: return themethod() 353 | except Exception, e: 354 | self.handler.response.set_status(400, "Can't call %r/%r: %s" % ( 355 | model, method, e)) 356 | return '' 357 | 358 | def do_get_entity_method(self, model, strid, method): 359 | """ Hook method to R/O call a method on an entity given s and strid. 360 | """ 361 | themethod = self.get_instance_method(model, method) 362 | if themethod is None: return '' 363 | entity = self.get_entity(model, strid) 364 | if entity is None: return '' 365 | try: return themethod(entity) 366 | except Exception, e: 367 | self.handler.response.set_status(400, "Can't call %r/%r/%r: %s" % ( 368 | model, strid, method, e)) 369 | return '' 370 | 371 | def get(self, prefix=None): 372 | """ Get JSON data for entity IDs of a model, or all about an entity. 373 | 374 | Depending on the request path, serve as JSON to the response object: 375 | - for a path of /classname/id, a jobj for that entity 376 | - for a path of /classname, a list of id-only jobjs for that model 377 | - or, the results of the method being called (should be R/O!) 378 | """ 379 | if not self.is_authorized(): 380 | self.handler.response.set_status(403, "You don't have permission to access on this server.") 381 | return self._serve({}) 382 | logging.info('GET path=%r, prefix=%r', self.handler.request.path, prefix) 383 | if self.__get_parser is None: 384 | self.__get_parser = parsutil.RestUrlParser(self.prefix_to_ignore, 385 | do_special_method=self.do_get_special_method, 386 | do_model=self.do_get_model, 387 | do_model_strid=self.do_get_entity, 388 | do_model_method=self.do_get_model_method, 389 | do_model_strid_method=self.do_get_entity_method, 390 | ) 391 | path = self.handler.request.path 392 | 393 | # hacky/kludgy special-case: serve all model names (TODO: remove this!) 394 | # (need to have proper %meta special w/methods to get such info!) 395 | if prefix is not None and path.strip('/') == prefix.strip('/'): 396 | result = restutil.allModelClassNames() 397 | logging.info('Hacky case (%r): %r', path, result) 398 | return self._serve(result) 399 | 400 | result = self.__get_parser.process(path, prefix) 401 | if result is None or isinstance(result, tuple): 402 | self.handler.response.set_status(400, 'Invalid URL for GET: %r' % path) 403 | return self._serve({}) 404 | return self._serve(result) 405 | 406 | # expose a single helper object, shd be reusable 407 | 408 | helper = JsonRestHelper() 409 | 410 | # just for testing...: 411 | import wsgiref.handlers 412 | from google.appengine.ext import webapp 413 | import models 414 | 415 | class _TestCrudRestHandler(webapp.RequestHandler): 416 | def __init__(self, *a, **k): 417 | webapp.RequestHandler.__init__(self, *a, **k) 418 | helper.hookup(self) 419 | 420 | def main(): 421 | logging.info('intgutil test main()') 422 | application = webapp.WSGIApplication([('/(rest)/.*', _TestCrudRestHandler)], 423 | debug=True) 424 | wsgiref.handlers.CGIHandler().run(application) 425 | 426 | if __name__ == '__main__': 427 | main() 428 | --------------------------------------------------------------------------------