initial commit

This commit is contained in:
Massimo Di Pierro
2011-11-22 23:30:42 -06:00
commit d421c1321b
580 changed files with 115527 additions and 0 deletions
+261
View File
@@ -0,0 +1,261 @@
"""
AIM class to credit card payment with authorize.net
Fork of authnet code written by John Conde
http://www.johnconde.net/blog/integrate-the-authorizenet-aim-api-with-python-3-2/
Unkown license, assuming public domain
Modifed by Massimo Di Pierro
- ported from Python 3.x run on Python 2.4+
- fixed a couple of bugs
- merged with test so single file
- namedtuple from http://code.activestate.com/recipes/500261/
"""
__all__ = ['AIM']
from operator import itemgetter
import urllib
_known_tuple_types = {}
class NamedTupleBase(tuple):
"""Base class for named tuples with the __new__ operator set, named tuples
yielded by the namedtuple() function will subclass this and add
properties."""
def __new__(cls, *args, **kws):
"""Create a new instance of this fielded tuple"""
# May need to unpack named field values here
if kws:
values = list(args) + [None]*(len(cls._fields) - len(args))
fields = dict((val, idx) for idx, val in enumerate(cls._fields))
for kw, val in kws.iteritems():
assert kw in kws, "%r not in field list" % kw
values[fields[kw]] = val
args = tuple(values)
return tuple.__new__(cls, args)
def namedtuple(typename, fieldnames):
"""
>>> import namedtuples
>>> tpl = namedtuples.namedtuple(['a', 'b', 'c'])
>>> tpl(1, 2, 3)
(1, 2, 3)
>>> tpl(1, 2, 3).b
2
>>> tpl(c=1, a=2, b=3)
(2, 3, 1)
>>> tpl(c=1, a=2, b=3).b
3
>>> tpl(c='pads with nones')
(None, None, 'pads with nones')
>>> tpl(b='pads with nones')
(None, 'pads with nones', None)
>>>
"""
# Split up a string, some people do this
if isinstance(fieldnames, basestring):
fieldnames = fieldnames.replace(',', ' ').split()
# Convert anything iterable that enumerates fields to a tuple now
fieldname_tuple = tuple(str(field) for field in fieldnames)
# See if we've cached this
if fieldname_tuple in _known_tuple_types:
return _known_tuple_types[fieldname_tuple]
# Make the type
new_tuple_type = type(typename, (NamedTupleBase,), {})
# Set the hidden field
new_tuple_type._fields = fieldname_tuple
# Add the getters
for i, field in enumerate(fieldname_tuple):
setattr(new_tuple_type, field, property(itemgetter(i)))
# Cache
_known_tuple_types[fieldname_tuple] = new_tuple_type
# Done
return new_tuple_type
class AIM:
class AIMError(Exception):
def __init__(self, value):
self.parameter = value
def __str__(self):
return str(self.parameter)
def __init__(self, login, transkey, testmode=False):
if str(login).strip() == '' or login == None:
raise AIM.AIMError('No login name provided')
if str(transkey).strip() == '' or transkey == None:
raise AIM.AIMError('No transaction key provided')
if testmode != True and testmode != False:
raise AIM.AIMError('Invalid value for testmode. Must be True or False. "{0}" given.'.format(testmode))
self.testmode = testmode
self.proxy = None;
self.delimiter = '|'
self.results = []
self.error = True
self.success = False
self.declined = False
self.parameters = {}
self.setParameter('x_delim_data', 'true')
self.setParameter('x_delim_char', self.delimiter)
self.setParameter('x_relay_response', 'FALSE')
self.setParameter('x_url', 'FALSE')
self.setParameter('x_version', '3.1')
self.setParameter('x_method', 'CC')
self.setParameter('x_type', 'AUTH_CAPTURE')
self.setParameter('x_login', login)
self.setParameter('x_tran_key', transkey)
def process(self):
encoded_args = urllib.urlencode(self.parameters)
if self.testmode == True:
url = 'https://test.authorize.net/gateway/transact.dll'
else:
url = 'https://secure.authorize.net/gateway/transact.dll'
if self.proxy == None:
self.results += str(urllib.urlopen(url, encoded_args).read()).split(self.delimiter)
else:
opener = urllib.FancyURLopener(self.proxy)
opened = opener.open(url, encoded_args)
try:
self.results += str(opened.read()).split(self.delimiter)
finally:
opened.close()
Results = namedtuple('Results', 'ResultResponse ResponseSubcode ResponseCode ResponseText AuthCode \
AVSResponse TransactionID InvoiceNumber Description Amount PaymentMethod \
TransactionType CustomerID CHFirstName CHLastName Company BillingAddress \
BillingCity BillingState BillingZip BillingCountry Phone Fax Email ShippingFirstName \
ShippingLastName ShippingCompany ShippingAddress ShippingCity ShippingState \
ShippingZip ShippingCountry TaxAmount DutyAmount FreightAmount TaxExemptFlag \
PONumber MD5Hash CVVResponse CAVVResponse')
self.response = Results(*tuple(r for r in self.results)[0:40])
if self.getResultResponseFull() == 'Approved':
self.error = False
self.success = True
self.declined = False
elif self.getResultResponseFull() == 'Declined':
self.error = False
self.success = False
self.declined = True
else:
raise AIM.AIMError(self.response.ResponseText)
def setTransaction(self, creditcard, expiration, total, cvv=None, tax=None, invoice=None):
if str(creditcard).strip() == '' or creditcard == None:
raise AIM.AIMError('No credit card number passed to setTransaction(): {0}'.format(creditcard))
if str(expiration).strip() == '' or expiration == None:
raise AIM.AIMError('No expiration number to setTransaction(): {0}'.format(expiration))
if str(total).strip() == '' or total == None:
raise AIM.AIMError('No total amount passed to setTransaction(): {0}'.format(total))
self.setParameter('x_card_num', creditcard)
self.setParameter('x_exp_date', expiration)
self.setParameter('x_amount', total)
if cvv != None:
self.setParameter('x_card_code', cvv)
if tax != None:
self.setParameter('x_tax', tax)
if invoice != None:
self.setParameter('x_invoice_num', invoice)
def setTransactionType(self, transtype=None):
types = ['AUTH_CAPTURE', 'AUTH_ONLY', 'PRIOR_AUTH_CAPTURE', 'CREDIT', 'CAPTURE_ONLY', 'VOID']
if transtype.upper() not in types:
raise AIM.AIMError('Incorrect Transaction Type passed to setTransactionType(): {0}'.format(transtype))
self.setParameter('x_type', transtype.upper())
def setProxy(self, proxy=None):
if str(proxy).strip() == '' or proxy == None:
raise AIM.AIMError('No proxy passed to setProxy()')
self.proxy = {'http': str(proxy).strip()}
def setParameter(self, key=None, value=None):
if key != None and value != None and str(key).strip() != '' and str(value).strip() != '':
self.parameters[key] = str(value).strip()
else:
raise AIM.AIMError('Incorrect parameters passed to setParameter(): {0}:{1}'.format(key, value))
def isApproved(self):
return self.success
def isDeclined(self):
return self.declined
def isError(self):
return self.error
def getResultResponseFull(self):
responses = ['', 'Approved', 'Declined', 'Error']
return responses[int(self.results[0])]
def process(creditcard,expiration,total,cvv=None,tax=None,invoice=None,
login='cnpdev4289', transkey='SR2P8g4jdEn7vFLQ',testmode=True):
payment = AIM(login,transkey,testmode)
expiration = expiration.replace('/','')
payment.setTransaction(creditcard, expiration, total, cvv, tax, invoice)
try:
payment.process()
return payment.isApproved()
except AIM.AIMError:
return False
def test():
import socket
import sys
from time import time
creditcard = '4427802641004797'
expiration = '122012'
total = '1.00'
cvv = '123'
tax = '0.00'
invoice = str(time())[4:10] # get a random invoice number
try:
payment = AIM('cnpdev4289', 'SR2P8g4jdEn7vFLQ', True)
payment.setTransaction(creditcard, expiration, total, cvv, tax, invoice)
payment.setParameter('x_duplicate_window', 180) # three minutes duplicate windows
payment.setParameter('x_cust_id', '1324') # customer ID
payment.setParameter('x_first_name', 'John')
payment.setParameter('x_last_name', 'Conde')
payment.setParameter('x_company', 'Test Company')
payment.setParameter('x_address', '1234 Main Street')
payment.setParameter('x_city', 'Townsville')
payment.setParameter('x_state', 'NJ')
payment.setParameter('x_zip', '12345')
payment.setParameter('x_country', 'US')
payment.setParameter('x_phone', '800-555-1234')
payment.setParameter('x_description', 'Test Transaction')
payment.setParameter('x_customer_ip', socket.gethostbyname(socket.gethostname()))
payment.setParameter('x_email', 'john@example.com')
payment.setParameter('x_email_customer', False)
payment.process()
if payment.isApproved():
print 'Response Code: ', payment.response.ResponseCode
print 'Response Text: ', payment.response.ResponseText
print 'Response: ', payment.getResultResponseFull()
print 'Transaction ID: ', payment.response.TransactionID
print 'CVV Result: ', payment.response.CVVResponse
print 'Approval Code: ', payment.response.AuthCode
print 'AVS Result: ', payment.response.AVSResponse
elif payment.isDeclined():
print 'Your credit card was declined by your bank'
elif payment.isError():
raise AIM.AIMError('An uncaught error occurred')
except AIM.AIMError, e:
print "Exception thrown:", e
print 'An error occured'
print 'approved',payment.isApproved()
print 'declined',payment.isDeclined()
print 'error',payment.isError()
if __name__=='__main__':
test()
+242
View File
@@ -0,0 +1,242 @@
"""
DowCommerce class to process credit card payments with DowCommerce.com
Modifications to support Dow Commerce API from code originally written by John Conde
http://www.johnconde.net/blog/integrate-the-authorizenet-aim-api-with-python-3-2/
Unkown license, assuming public domain
Modifed by Dave Stoll dave.stoll@gmail.com
- modifed to support Dow Commerce API
"""
__all__ = ['DowCommerce']
from operator import itemgetter
import urllib
class DowCommerce:
class DowCommerceError(Exception):
def __init__(self, value):
self.parameter = value
def __str__(self):
return str(self.parameter)
def __init__(self, username=None, password=None, demomode=False):
if not demomode:
if str(username).strip() == '' or username == None:
raise DowCommerce.DowCommerceError('No username provided')
if str(password).strip() == '' or password == None:
raise DowCommerce.DowCommerceError('No password provided')
else:
username = 'demo'
password = 'password'
self.proxy = None;
self.delimiter = '&'
self.results = {}
self.error = True
self.success = False
self.declined = False
self.url = 'https://secure.dowcommerce.net/api/transact.php'
self.parameters = {}
self.setParameter('username', username)
self.setParameter('password', password)
def process(self):
encoded_args = urllib.urlencode(self.parameters)
if self.proxy == None:
results = str(urllib.urlopen(self.url, encoded_args).read()).split(self.delimiter)
else:
opener = urllib.FancyURLopener(self.proxy)
opened = opener.open(self.url, encoded_args)
try:
results += str(opened.read()).split(self.delimiter)
finally:
opened.close()
for result in results:
(key,val) = result.split('=')
self.results[key] = val
if self.results['response'] == '1':
self.error = False
self.success = True
self.declined = False
elif self.results['response'] == '2':
self.error = False
self.success = False
self.declined = True
elif self.results['response'] == '3':
self.error = True
self.success = False
self.declined = False
else:
self.error = True
self.success = False
self.declined = False
raise DowCommerce.DowCommerceError(self.results)
def setTransaction(self, creditcard, expiration, total, cvv=None, orderid=None, orderdescription=None,
ipaddress=None, tax=None, shipping=None,
firstname=None, lastname=None, company=None, address1=None, address2=None, city=None, state=None, zipcode=None,
country=None, phone=None, fax=None, emailaddress=None, website=None,
shipping_firstname=None, shipping_lastname=None, shipping_company=None, shipping_address1=None, shipping_address2=None,
shipping_city=None, shipping_state=None, shipping_zipcode = None, shipping_country=None, shipping_emailaddress=None):
if str(creditcard).strip() == '' or creditcard == None:
raise DowCommerce.DowCommerceError('No credit card number passed to setTransaction(): {0}'.format(creditcard))
if str(expiration).strip() == '' or expiration == None:
raise DowCommerce.DowCommerceError('No expiration number passed to setTransaction(): {0}'.format(expiration))
if str(total).strip() == '' or total == None:
raise DowCommerce.DowCommerceError('No total amount passed to setTransaction(): {0}'.format(total))
self.setParameter('ccnumber', creditcard)
self.setParameter('ccexp', expiration)
self.setParameter('amount', total)
if cvv:
self.setParameter('cvv', cvv)
if orderid:
self.setParameter('orderid', orderid)
if orderdescription:
self.setParameter('orderdescription', orderdescription)
if ipaddress:
self.setParameter('ipaddress', ipaddress)
if tax:
self.setParameter('tax', tax)
if shipping:
self.setParameter('shipping', shipping)
## billing info
if firstname:
self.setParameter('firstname', firstname)
if lastname:
self.setParameter('lastname', lastname)
if company:
self.setParameter('company', company)
if address1:
self.setParameter('address1', address1)
if address2:
self.setParameter('address2', address2)
if city:
self.setParameter('city', city)
if state:
self.setParameter('state', state)
if zipcode:
self.setParameter('zip', zipcode)
if country:
self.setParameter('country', country)
if phone:
self.setParameter('phone', phone)
if fax:
self.setParameter('fax', fax)
if emailaddress:
self.setParameter('email', emailaddress)
if website:
self.setParameter('website', website)
## shipping info
if shipping_firstname:
self.setParameter('shipping_firstname', shipping_firstname)
if shipping_lastname:
self.setParameter('shipping_lastname', shipping_lastname)
if shipping_company:
self.setParameter('shipping_company', shipping_company)
if shipping_address1:
self.setParameter('shipping_address1', shipping_address1)
if shipping_address2:
self.setParameter('shipping_address2', shipping_address2)
if shipping_city:
self.setParameter('shipping_city', shipping_city)
if shipping_state:
self.setParameter('shipping_state', shipping_state)
if shipping_zipcode:
self.setParameter('shipping_zip', shipping_zipcode)
if shipping_country:
self.setParameter('shipping_country', shipping_country)
def setTransactionType(self, transtype=None):
types = ['sale', 'auth', 'credit']
if transtype.lower() not in types:
raise DowCommerce.DowCommerceError('Incorrect Transaction Type passed to setTransactionType(): {0}'.format(transtype))
self.setParameter('type', transtype.lower())
def setProxy(self, proxy=None):
if str(proxy).strip() == '' or proxy == None:
raise DowCommerce.DowCommerceError('No proxy passed to setProxy()')
self.proxy = {'http': str(proxy).strip()}
def setParameter(self, key=None, value=None):
if key != None and value != None and str(key).strip() != '' and str(value).strip() != '':
self.parameters[key] = str(value).strip()
else:
raise DowCommerce.DowCommerceError('Incorrect parameters passed to setParameter(): {0}:{1}'.format(key, value))
def isApproved(self):
return self.success
def isDeclined(self):
return self.declined
def isError(self):
return self.error
def getResultResponseShort(self):
responses = ['', 'Approved', 'Declined', 'Error']
return responses[int(self.results['response'])]
def getFullResponse(self):
return self.results
def getResponseText(self):
return self.results['responsetext']
def test():
import socket
import sys
from time import time
## TEST VALUES FROM API DOC:
# Visa: 4111111111111111
# MasterCard 5431111111111111
# DiscoverCard: 6011601160116611
# American Express: 341111111111111
# Expiration: 10/10
# Amount: > 1.00 (( passing less than $1.00 will cause it to be declined ))
# CVV: 999
creditcard = '4111111111111111'
expiration = '1010'
total = '1.00'
cvv = '999'
tax = '0.00'
orderid = str(time())[4:10] # get a random invoice number
try:
payment = DowCommerce(demomode=True)
payment.setTransaction(creditcard, expiration, total, cvv=cvv, tax=tax, orderid=orderid, orderdescription='Test Transaction',
firstname='John', lastname='Doe', company='Acme', address1='123 Min Street', city='Hometown', state='VA',
zipcode='12345', country='US', phone='888-555-1212', emailaddress='john@noemail.local', ipaddress='192.168.1.1')
payment.process()
if payment.isApproved():
print 'Payment approved!'
print payment.getFullResponse()
elif payment.isDeclined():
print 'Your credit card was declined by your bank'
elif payment.isError():
raise DowCommerce.DowCommerceError('An uncaught error occurred')
except DowCommerce.DowCommerceError, e:
print "Exception thrown:", e
print 'An error occured'
print 'approved',payment.isApproved()
print 'declined',payment.isDeclined()
print 'error',payment.isError()
if __name__=='__main__':
test()
+3
View File
@@ -0,0 +1,3 @@
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/python
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Attention: Requires Chrome or Safari. For IE of Firefox you need https://github.com/gimite/web-socket-js
1) install tornado (requires Tornado 2.1)
easy_install tornado
2) start this app:
python gluon/contrib/comet_messaging.py -k mykey -p 8888
3) from any web2py app you can post messages with
from gluon.contrib.comet_messaging import comet_send
comet_send('http://127.0.0.1:8888','Hello World','mykey','mygroup')
4) from any template you can receive them with
<script>
$(document).ready(function(){
if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',function(e){alert(e.data)}))
alert("html5 websocket not supported by your browser, try Google Chrome");
});
</script>
When the server posts a message, all clients connected to the page will popup an alert message
Or if you want to send json messages and store evaluated json in a var called data:
<script>
$(document).ready(function(){
var data;
web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',function(e){data=eval('('+e.data+')')});
});
</script>
- All communications between web2py and comet_messaging will be digitally signed with hmac.
- All validation is handled on the web2py side and there is no need to modify comet_messaging.py
- Multiple web2py instances can talk with one or more comet_messaging servers.
- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the comet_messaging server.
- Via group='mygroup' name you can support multiple groups of clients (think of many chat-rooms)
Here is a complete sample web2py action:
def index():
form=LOAD('default','ajax_form',ajax=True)
script=SCRIPT('''
jQuery(document).ready(function(){
var callback=function(e){alert(e.data)};
if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',callback))
alert("html5 websocket not supported by your browser, try Google Chrome");
});
''')
return dict(form=form, script=script)
def ajax_form():
form=SQLFORM.factory(Field('message'))
if form.accepts(request,session):
from gluon.contrib.comet_messaging import comet_send
comet_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
return form
Acknowledgements:
Tornado code inspired by http://thomas.pelletier.im/2010/08/websocket-tornado-redis/
"""
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import hmac
import sys
import optparse
import urllib
import time
listeners = {}
names = {}
tokens = {}
def comet_send(url,message,hmac_key=None,group='default'):
sig = hmac_key and hmac.new(hmac_key,message).hexdigest() or ''
params = urllib.urlencode({'message': message, 'signature': sig, 'group':group})
f = urllib.urlopen(url, params)
data= f.read()
f.close()
return data
class PostHandler(tornado.web.RequestHandler):
"""
only authorized parties can post messages
"""
def post(self):
if hmac_key and not 'signature' in self.request.arguments: return 'false'
if 'message' in self.request.arguments:
message = self.request.arguments['message'][0]
group = self.request.arguments.get('group',['default'])[0]
print '%s:MESSAGE to %s:%s' % (time.time(), group, message)
if hmac_key:
signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key,message).hexdigest()==signature: return 'false'
for client in listeners.get(group,[]): client.write_message(message)
return 'true'
return 'false'
class TokenHandler(tornado.web.RequestHandler):
"""
if running with -t post a token to allow a client to join using the token
the message here is the token (any uuid)
allows only authorized parties to joins, for example, a chat
"""
def post(self):
if hmac_key and not 'message' in self.request.arguments: return 'false'
if 'message' in self.request.arguments:
message = self.request.arguments['message'][0]
if hmac_key:
signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key,message).hexdigest()==signature: return 'false'
tokens[message] = None
return 'true'
return 'false'
class DistributeHandler(tornado.websocket.WebSocketHandler):
def open(self,params):
group,token,name = params.split('/')+[None,None]
self.group = group or 'default'
self.token = token or 'none'
self.name = name or 'anonymous'
# only authorized parties can join
if DistributeHandler.tokens:
if not self.token in tokens or not token[self.token]==None:
self.close()
else:
tokens[self.token] = self
if not self.group in listeners: listeners[self.group]=[]
# notify clients that a member has joined the groups
for client in listeners.get(self.group,[]): client.write_message('+'+self.name)
listeners[self.group].append(self)
names[self] = self.name
print '%s:CONNECT to %s' % (time.time(), self.group)
def on_message(self, message):
pass
def on_close(self):
if self.group in listeners: listeners[self.group].remove(self)
del names[self]
# notify clients that a member has left the groups
for client in listeners.get(self.group,[]): client.write_message('-'+self.name)
print '%s:DISCONNECT from %s' % (time.time(), self.group)
if __name__ == "__main__":
usage = __doc__
version= ""
parser = optparse.OptionParser(usage, None, optparse.Option, version)
parser.add_option('-p',
'--port',
default='8888',
dest='port',
help='socket')
parser.add_option('-l',
'--listen',
default='0.0.0.0',
dest='address',
help='listener address')
parser.add_option('-k',
'--hmac_key',
default='',
dest='hmac_key',
help='hmac_key')
parser.add_option('-t',
'--tokens',
action='store_true',
default=False,
dest='tokens',
help='require tockens to join')
(options, args) = parser.parse_args()
hmac_key = options.hmac_key
DistributeHandler.tokens = options.tokens
urls=[
(r'/', PostHandler),
(r'/token', TokenHandler),
(r'/realtime/(.*)', DistributeHandler)]
application = tornado.web.Application(urls, auto_reload=True)
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(int(options.port), address=options.address)
tornado.ioloop.IOLoop.instance().start()
+3908
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Developed by Robin Bhattacharyya (memecache for GAE)
Released under the web2py license (LGPL)
from gluon.contrib.gae_memcache import MemcacheClient
cache.ram=cache.disk=MemcacheClient(request)
"""
import time
from google.appengine.api.memcache import Client
class MemcacheClient(Client):
def __init__(self, request):
self.request = request
Client.__init__(self)
def __call__(
self,
key,
f,
time_expire=300,
):
key = '%s/%s' % (self.request.application, key)
dt = time_expire
value = None
obj = self.get(key)
if obj and (dt == None or obj[0] > time.time() - dt):
value = obj[1]
elif f is None:
if obj:
self.delete(key)
else:
value = f()
self.set(key, (time.time(), value))
return value
def increment(self, key, value=1):
key = '%s/%s' % (self.request.application, key)
obj = self.get(key)
if obj:
value = obj[1] + value
self.set((time.time(), value))
return value
def clear(self, key):
key = '%s/%s' % (self.request.application, key)
self.delete(key)
+88
View File
@@ -0,0 +1,88 @@
def autoretry_datastore_timeouts(attempts=5.0, interval=0.1, exponent=2.0):
"""
Copyright (C) 2009 twitter.com/rcb
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
======================================================================
This function wraps the AppEngine Datastore API to autoretry
datastore timeouts at the lowest accessible level.
The benefits of this approach are:
1. Small Footprint: Does not monkey with Model internals
which may break in future releases.
2. Max Performance: Retrying at this lowest level means
serialization and key formatting is not
needlessly repeated on each retry.
At initialization time, execute this:
>>> autoretry_datastore_timeouts()
Should only be called once, subsequent calls have no effect.
>>> autoretry_datastore_timeouts() # no effect
Default (5) attempts: .1, .2, .4, .8, 1.6 seconds
Parameters can each be specified as floats.
:param attempts: maximum number of times to retry.
:param interval: base seconds to sleep between retries.
:param exponent: rate of exponential back-off.
"""
import time, logging
from google.appengine.api import apiproxy_stub_map
from google.appengine.runtime import apiproxy_errors
from google.appengine.datastore import datastore_pb
attempts = float(attempts)
interval = float(interval)
exponent = float(exponent)
wrapped = apiproxy_stub_map.MakeSyncCall
errors = {datastore_pb.Error.TIMEOUT:'Timeout',
datastore_pb.Error.CONCURRENT_TRANSACTION:'TransactionFailedError'}
def wrapper(*args, **kwargs):
count = 0.0
while True:
try:
return wrapped(*args, **kwargs)
except apiproxy_errors.ApplicationError, err:
errno = err.application_error
if errno not in errors: raise
sleep = (exponent ** count) * interval
count += 1.0
if count > attempts: raise
msg = "Datastore %s: retry #%d in %s seconds.\n%s"
vals = ''
if count == 1.0:
vals = '\n'.join([str(a) for a in args])
logging.warning(msg % (errors[errno], count, sleep, vals))
time.sleep(sleep)
setattr(wrapper, '_autoretry_datastore_timeouts', False)
if getattr(wrapped, '_autoretry_datastore_timeouts', True):
apiproxy_stub_map.MakeSyncCall = wrapper
+2
View File
@@ -0,0 +1,2 @@
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
# fix response
import re
import os
import cPickle
import gluon.serializers
from gluon import current
from gluon.html import markmin_serializer, TAG, HTML, BODY, UL, XML
from gluon.contenttype import contenttype
from gluon.contrib.pyfpdf import FPDF, HTMLMixin
from gluon.sanitizer import sanitize
from gluon.contrib.markmin.markmin2latex import markmin2latex
from gluon.contrib.markmin.markmin2pdf import markmin2pdf
def wrapper(f):
def g(data):
try:
output = f(data)
except (TypeError, ValueError):
raise HTTP(405, '%s serialization error' % extension.upper())
except ImportError:
raise HTTP(405, '%s not available' % extension.upper())
except:
raise HTTP(405, '%s error' % extension.upper())
return XML(ouput)
return g
def latex_from_html(html):
markmin=TAG(html).element('body').flatten(markmin_serializer)
return XML(markmin2latex(markmin))
def pdflatex_from_html(html):
if os.system('which pdflatex > /dev/null')==0:
markmin=TAG(html).element('body').flatten(markmin_serializer)
out,warning,errors=markmin2pdf(markmin)
if errors:
current.response.headers['Content-Type']='text/html'
raise HTTP(405,HTML(BODY(H1('errors'),
LU(*errors),
H1('warnings'),
LU(*warnings))).xml())
else:
return XML(out)
def pyfpdf_from_html(html):
request = current.request
def image_map(path):
if path.startswith('/%s/static/' % request.application):
return os.path.join(request.folder,path.split('/',2)[2])
return 'http%s://%s%s' % (request.is_https and 's' or '',request.env.http_host, path)
class MyFPDF(FPDF, HTMLMixin): pass
pdf=MyFPDF()
pdf.add_page()
html = sanitize(html, escape=False) #### should have better list of allowed tags
pdf.write_html(html,image_map=image_map)
return XML(pdf.output(dest='S'))
def pdf_from_html(html):
# try use latex and pdflatex
if os.system('which pdflatex > /dev/null')==0:
return pdflatex_from_html(html)
else:
return pyfpdf_from_html(html)
+17
View File
@@ -0,0 +1,17 @@
from gluon import XML
def button(merchant_id="123456789012345",
products=[dict(name="shoes",
quantity=1,
price=23.5,
currency='USD',
description="running shoes black")]):
t = '<input name="item_%(key)s_%(k)s" type="hidden" value="%(value)s"/>'
list_products = ''
for k,product in enumerate(products):
for key,value in product.items():
list_products += t % dict(k=k+1,key=key,value=value)
button = '<form action="https://checkout.google.com/api/checkout/v2/checkoutForm/Merchant/%s" id="BB_BuyButtonForm" method="post" name="BB_BuyButtonForm" target="_top">%s<input name="_charset_" type="hidden" value="utf-8"/><input alt="" src="https://checkout.google.com/buttons/buy.gif?merchant_id=%s&amp;w=117&amp;h=48&amp;style=white&amp;variant=text&amp;loc=en_US" type="image"/></form>' % (merchant_id, list_products, merchant_id)
return XML(button)
+7
View File
@@ -0,0 +1,7 @@
# this file exists for backward compatibility
__all__ = ['DAL','Field','drivers','gae']
from gluon.dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType, gae
+2
View File
@@ -0,0 +1,2 @@
+25
View File
@@ -0,0 +1,25 @@
import urllib
import urllib2
import base64
def basic_auth(server="http://127.0.0.1"):
"""
to use basic login with a different server
from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods.append(basic_auth('http://server'))
"""
def basic_login_aux(username,
password,
server=server):
key = base64.b64encode(username+':'+password)
headers = {'Authorization': 'Basic ' + key}
request = urllib2.Request(server, None, headers)
try:
urllib2.urlopen(request)
return True
except (urllib2.URLError, urllib2.HTTPError):
return False
return basic_login_aux
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of web2py Web Framework (Copyrighted, 2007-2009).
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>.
License: GPL v2
Tinkered by Szabolcs Gyuris < szimszo n @ o regpreshaz dot eu>
"""
from gluon import current, redirect
class CasAuth( object ):
"""
Login will be done via Web2py's CAS application, instead of web2py's
login form.
Include in your model (eg db.py)::
from gluon.contrib.login_methods.cas_auth import CasAuth
auth.define_tables(username=True)
auth.settings.login_form=CasAuth(
urlbase = "https://[your CAS provider]/app/default/user/cas",
actions=['login','validate','logout'])
where urlbase is the actual CAS server url without the login,logout...
Enjoy.
###UPDATE###
if you want to connect to a CAS version 2 JASIG Server use this:
auth.settings.login_form=CasAuth(
urlbase = "https://[Your CAS server]/cas",
actions = ['login','serviceValidate','logout'],
casversion = 2,
casusername = "cas:user")
where casusername is the xml node returned by CAS server which contains
user's username.
"""
def __init__(self, g=None, ### g for backward compatibility ###
urlbase = "https://web2py.com/cas/cas",
actions=['login','check','logout'],
maps=dict(username=lambda v:v.get('username',v['user']),
email=lambda v:v.get('email',None),
user_id=lambda v:v['user']),
casversion = 1,
casusername = 'cas:user'
):
self.urlbase=urlbase
self.cas_login_url="%s/%s"%(self.urlbase,actions[0])
self.cas_check_url="%s/%s"%(self.urlbase,actions[1])
self.cas_logout_url="%s/%s"%(self.urlbase,actions[2])
self.maps=maps
self.casversion = casversion
self.casusername = casusername
http_host=current.request.env.http_x_forwarded_host
if not http_host: http_host=current.request.env.http_host
if current.request.env.wsgi_url_scheme in [ 'https', 'HTTPS' ]:
scheme = 'https'
else:
scheme = 'http'
self.cas_my_url='%s://%s%s'%( scheme, http_host, current.request.env.path_info )
def login_url( self, next = "/" ):
current.session.token=self._CAS_login()
return next
def logout_url( self, next = "/" ):
current.session.token=None
current.session.auth=None
self._CAS_logout()
return next
def get_user( self ):
user=current.session.token
if user:
d = {'source':'web2py cas'}
for key in self.maps:
d[key]=self.maps[key](user)
return d
return None
def _CAS_login( self ):
"""
exposed as CAS.login(request)
returns a token on success, None on failed authentication
"""
import urllib
self.ticket=current.request.vars.ticket
if not current.request.vars.ticket:
redirect( "%s?service=%s"% (self.cas_login_url,
self.cas_my_url))
else:
url="%s?service=%s&ticket=%s" % (self.cas_check_url,
self.cas_my_url,
self.ticket )
data=urllib.urlopen( url ).read()
if data.startswith('yes') or data.startswith('no'):
data = data.split('\n')
if data[0]=='yes':
a,b,c = data[1].split( ':' )+[None,None]
return dict(user=a,email=b,username=c)
return None
import xml.dom.minidom as dom
import xml.parsers.expat as expat
try:
dxml=dom.parseString(data)
envelop = dxml.getElementsByTagName("cas:authenticationSuccess")
if len(envelop)>0:
res = dict()
for x in envelop[0].childNodes:
if x.nodeName.startswith('cas:') and len(x.childNodes):
key = x.nodeName[4:].encode('utf8')
value = x.childNodes[0].nodeValue.encode('utf8')
if not key in res:
res[key]=value
else:
if not isinstance(res[key],list):
res[key]=[res[key]]
res[key].append(value)
return res
except expat.ExpatError: pass
return None # fallback
def _CAS_logout( self ):
"""
exposed CAS.logout()
redirects to the CAS logout page
"""
import urllib
redirect("%s?service=%s" % (self.cas_logout_url,self.cas_my_url))
@@ -0,0 +1,109 @@
#!/usr/bin/env python
# coding: utf8
"""
Dropbox Authentication for web2py
Developed by Massimo Di Pierro (2011)
Same License as Web2py License
"""
# mind here session is dropbox session, not current.session
import os
import re
import urllib
from dropbox import client, rest, session
from gluon import *
from gluon.tools import fetch
from gluon.storage import Storage
import gluon.contrib.simplejson as json
class DropboxAccount(object):
"""
from gluon.contrib.login_methods.dropbox_account import DropboxAccount
auth.settings.actions_disabled=['register','change_password','request_reset_password']
auth.settings.login_form = DropboxAccount(request,
key="...",
secret="...",
access_type="...",
url = "http://localhost:8000/%s/default/user/login" % request.application)
when logged in
client = auth.settings.login_form.client
"""
def __init__(self,
request,
key = "",
secret = "",
access_type="app_folder",
login_url = "",
on_login_failure=None,
):
self.request=request
self.key=key
self.secret=secret
self.access_type=access_type
self.login_url = login_url
self.on_login_failure = on_login_failure
self.sess = session.DropboxSession(
self.key,self.secret,self.access_type)
def get_user(self):
request = self.request
token = current.session.dropbox_token
try:
access_token = self.sess.obtain_access_token(token)
except:
access_token = None
if access_token:
user = Storage()
self.client = client.DropboxClient(self.sess)
data = self.client.account_info()
display_name = data.get('display_name','').split(' ',1)
user = dict(email = data.get('email',None),
first_name = display_name[0],
last_name = display_name[-1],
registration_id = data.get('uid',None))
if not user['registration_id'] and self.on_login_failure:
redirect(self.on_login_failure)
return user
return None
def login_form(self):
token = self.sess.obtain_request_token()
current.session.dropbox_token = token
dropbox_url = self.sess.build_authorize_url(token,self.login_url)
redirect(dropbox_url)
form = IFRAME(_src=dropbox_url,
_scrolling="no",
_frameborder="no",
_style="width:400px;height:240px;")
return form
def logout_url(self, next = "/"):
current.session.dropbox_token=None
current.session.auth=None
redirect('https://www.dropbox.com/logout')
return next
def put(self,filename,file):
return json.loads(self.client.put_file(filename,file))['bytes']
def get(self,filename,file):
return self.client.get_file(filename)
def dir(self,path):
return json.loads(self.client.metadata(path))
def use_dropbox(auth,filename='private/dropbox.key',**kwargs):
path = os.path.join(current.request.folder,filename)
if os.path.exists(path):
request = current.request
key,secret,access_type = open(path,'r').read().strip().split(':')
host = current.request.env.http_host
login_url = "http://%s/%s/default/user/login" % \
(host,request.application)
auth.settings.actions_disabled = \
['register','change_password','request_reset_password']
auth.settings.login_form = DropboxAccount(
request,key=key,secret=secret,access_type=access_type,
login_url = login_url,**kwargs)
+45
View File
@@ -0,0 +1,45 @@
import smtplib
import logging
def email_auth(server="smtp.gmail.com:587",
domain="@gmail.com",
tls_mode=None):
"""
to use email_login:
from gluon.contrib.login_methods.email_auth import email_auth
auth.settings.login_methods.append(email_auth("smtp.gmail.com:587",
"@gmail.com"))
"""
def email_auth_aux(email,
password,
server=server,
domain=domain,
tls_mode=tls_mode):
if domain:
if not isinstance(domain,(list,tuple)):
domain=[str(domain)]
if not [d for d in domain if email[-len(d):]==d]:
return False
(host, port) = server.split(':')
if tls_mode is None: # then auto detect
tls_mode = port == '587'
try:
server = None
server = smtplib.SMTP(host, port)
server.ehlo()
if tls_mode:
server.starttls()
server.ehlo()
server.login(email, password)
server.quit()
return True
except:
logging.exception('email_auth() failed')
if server:
try:
server.quit()
except: # server might already close connection after error
pass
return False
return email_auth_aux
@@ -0,0 +1,103 @@
#!/usr/bin/env python
# coding: utf8
"""
ExtendedLoginForm is used to extend normal login form in web2py with one more login method.
So user can choose the built-in login or extended login methods.
"""
from gluon.html import DIV
class ExtendedLoginForm(object):
"""
Put extended_login_form under web2py/gluon/contrib/login_methods folder.
Then inside your model where defines the auth:
auth = Auth(globals(),db) # authentication/authorization
...
auth.define_tables() # You might like to put the code after auth.define_tables
... # if the alt_login_form deals with tables of auth.
alt_login_form = RPXAccount(request,
api_key="...",
domain="...",
url = "http://localhost:8000/%s/default/user/login" % request.application)
extended_login_form = ExtendedLoginForm(auth, alt_login_form, signals=['token'])
auth.settings.login_form = extended_login_form
Note:
Since rpx_account doesn't create the password for the user, you
might need to provide a way for user to create password to do
normal login.
"""
def __init__(self,
auth,
alt_login_form,
signals=[],
login_arg = 'login'
):
self.auth = auth
self.alt_login_form = alt_login_form
self.signals = signals
self.login_arg = login_arg
def get_user(self):
"""
Delegate the get_user to alt_login_form.get_user.
"""
if hasattr(self.alt_login_form, 'get_user'):
return self.alt_login_form.get_user()
return None # let gluon.tools.Auth.get_or_create_user do the rest
def login_url(self, next):
"""
Optional implement for alt_login_form.
In normal case, this should be replaced by get_user, and never get called.
"""
if hasattr(self.alt_login_form, 'login_url'):
return self.alt_login_form.login_url(next)
return self.auth.settings.login_url
def logout_url(self, next):
"""
Optional implement for alt_login_form.
Called if bool(alt_login_form.get_user) is True.
If alt_login_form implemented logout_url function, it will return that function call.
"""
if hasattr(self.alt_login_form, 'logout_url'):
return self.alt_login_form.logout_url(next)
return next
def login_form(self):
"""
Combine the auth() form with alt_login_form.
If signals are set and a parameter in request matches any signals,
it will return the call of alt_login_form.login_form instead.
So alt_login_form can handle some particular situations, for example,
multiple steps of OpenID login inside alt_login_form.login_form.
Otherwise it will render the normal login form combined with
alt_login_form.login_form.
"""
request = self.auth.environment.request
args = request.args
if (self.signals and
any([True for signal in self.signals if request.vars.has_key(signal)])
):
return self.alt_login_form.login_form()
self.auth.settings.login_form = self.auth
form = DIV(self.auth())
self.auth.settings.login_form = self
form.components.append(self.alt_login_form.login_form())
return form
@@ -0,0 +1,38 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of web2py Web Framework (Copyrighted, 2007-2009).
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>.
License: GPL v2
Thanks to Hans Donner <hans.donner@pobox.com> for GaeGoogleAccount.
"""
from google.appengine.api import users
class GaeGoogleAccount(object):
"""
Login will be done via Google's Appengine login object, instead of web2py's
login form.
Include in your model (eg db.py)::
from gluon.contrib.login_methods.gae_google_account import \
GaeGoogleAccount
auth.settings.login_form=GaeGoogleAccount()
"""
def login_url(self, next="/"):
return users.create_login_url(next)
def logout_url(self, next="/"):
return users.create_logout_url(next)
def get_user(self):
user = users.get_current_user()
if user:
return dict(nickname=user.nickname(), email=user.email(),
user_id=user.user_id(), source="google account")
+177
View File
@@ -0,0 +1,177 @@
import sys
import logging
try:
import ldap
ldap.set_option(ldap.OPT_REFERRALS, 0)
except Exception, e:
logging.error('missing ldap, try "easy_install python-ldap"')
raise e
def ldap_auth(server='ldap', port=None,
base_dn='ou=users,dc=domain,dc=com',
mode='uid', secure=False, cert_path=None, bind_dn=None, bind_pw=None, filterstr='objectClass=*'):
"""
to use ldap login with MS Active Directory::
from gluon.contrib.login_methods.ldap_auth import ldap_auth
auth.settings.login_methods.append(ldap_auth(
mode='ad', server='my.domain.controller',
base_dn='ou=Users,dc=domain,dc=com'))
to use ldap login with Notes Domino::
auth.settings.login_methods.append(ldap_auth(
mode='domino',server='my.domino.server'))
to use ldap login with OpenLDAP::
auth.settings.login_methods.append(ldap_auth(
server='my.ldap.server', base_dn='ou=Users,dc=domain,dc=com'))
to use ldap login with OpenLDAP and subtree search and (optionally) multiple DNs:
auth.settings.login_methods.append(ldap_auth(
mode='uid_r', server='my.ldap.server',
base_dn=['ou=Users,dc=domain,dc=com','ou=Staff,dc=domain,dc=com']))
or (if using CN)::
auth.settings.login_methods.append(ldap_auth(
mode='cn', server='my.ldap.server',
base_dn='ou=Users,dc=domain,dc=com'))
If using secure ldaps:// pass secure=True and cert_path="..."
If you need to bind to the directory with an admin account in order to search it then specify bind_dn & bind_pw to use for this.
- currently only implemented for Active Directory
If you need to restrict the set of allowed users (e.g. to members of a department) then specify
a rfc4515 search filter string.
- currently only implemented for mode in ['ad', 'company', 'uid_r']
"""
def ldap_auth_aux(username,
password,
ldap_server=server,
ldap_port=port,
ldap_basedn=base_dn,
ldap_mode=mode,
ldap_binddn=bind_dn,
ldap_bindpw=bind_pw,
secure=secure,
cert_path=cert_path,
filterstr=filterstr):
try:
if secure:
if not ldap_port:
ldap_port = 636
con = ldap.initialize(
"ldaps://" + ldap_server + ":" + str(ldap_port))
if cert_path:
con.set_option(ldap.OPT_X_TLS_CACERTDIR, cert_path)
else:
if not ldap_port:
ldap_port = 389
con = ldap.initialize(
"ldap://" + ldap_server + ":" + str(ldap_port))
if ldap_mode == 'ad':
# Microsoft Active Directory
if '@' not in username:
domain = []
for x in ldap_basedn.split(','):
if "DC=" in x.upper():
domain.append(x.split('=')[-1])
username = "%s@%s" % (username, '.'.join(domain))
username_bare = username.split("@")[0]
con.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
# In cases where ForestDnsZones and DomainDnsZones are found,
# result will look like the following:
# ['ldap://ForestDnsZones.domain.com/DC=ForestDnsZones,DC=domain,DC=com']
if not isinstance(result, dict):
# result should be a dict in the form {'sAMAccountName': [username_bare]}
return False
if ldap_binddn:
# need to search directory with an admin account 1st
con.simple_bind_s(ldap_binddn, ldap_bindpw)
else:
# credentials should be in the form of username@domain.tld
con.simple_bind_s(username, password)
# this will throw an index error if the account is not found
# in the ldap_basedn
result = con.search_ext_s(
ldap_basedn, ldap.SCOPE_SUBTREE,
"(&(sAMAccountName=%s)(%s))" % (username_bare, filterstr), ["sAMAccountName"])[0][1]
if ldap_binddn:
# We know the user exists & is in the correct OU
# so now we just check the password
con.simple_bind_s(username, password)
if ldap_mode == 'domino':
# Notes Domino
if "@" in username:
username = username.split("@")[0]
con.simple_bind_s(username, password)
if ldap_mode == 'cn':
# OpenLDAP (CN)
dn = "cn=" + username + "," + ldap_basedn
con.simple_bind_s(dn, password)
if ldap_mode == 'uid':
# OpenLDAP (UID)
dn = "uid=" + username + "," + ldap_basedn
con.simple_bind_s(dn, password)
if ldap_mode == 'company':
# no DNs or password needed to search directory
dn = ""
pw = ""
# bind anonymously
con.simple_bind_s(dn, pw)
# search by e-mail address
filter = '(&(mail=' + username + ')(' + filterstr + '))'
# find the uid
attrs = ['uid']
# perform the actual search
company_search_result=con.search_s(ldap_basedn,
ldap.SCOPE_SUBTREE,
filter, attrs)
dn = company_search_result[0][0]
# perform the real authentication test
con.simple_bind_s(dn, password)
if ldap_mode == 'uid_r':
# OpenLDAP (UID) with subtree search and multiple DNs
if type(ldap_basedn) == type([]):
basedns = ldap_basedn
else:
basedns = [ldap_basedn]
filter = '(&(uid=%s)(%s))' % (username, filterstr)
for basedn in basedns:
try:
result = con.search_s(basedn, ldap.SCOPE_SUBTREE, filter)
if result:
user_dn = result[0][0]
# Check the password
con.simple_bind_s(user_dn, password)
con.unbind()
return True
except ldap.LDAPError, detail:
(exc_type, exc_value) = sys.exc_info()[:2]
sys.stderr.write("ldap_auth: searching %s for %s resulted in %s: %s\n" %
(basedn, filter, exc_type, exc_value))
return False
con.unbind()
return True
except ldap.LDAPError, e:
return False
except IndexError, ex: # for AD membership test
return False
if filterstr[0] == '(' and filterstr[-1] == ')': # rfc4515 syntax
filterstr = filterstr[1:-1] # parens added again where used
return ldap_auth_aux
@@ -0,0 +1,51 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of web2py Web Framework (Copyrighted, 2007-2009).
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>.
License: GPL v2
Thanks to Hans Donner <hans.donner@pobox.com> for GaeGoogleAccount.
"""
from gluon.http import HTTP
try:
import linkedin
except ImportError:
raise HTTP(400,"linkedin module not found")
class LinkedInAccount(object):
"""
Login will be done via Google's Appengine login object, instead of web2py's
login form.
Include in your model (eg db.py)::
from gluon.contrib.login_methods.linkedin_account import LinkedInAccount
auth.settings.login_form=LinkedInAccount(request,KEY,SECRET,RETURN_URL)
"""
def __init__(self,request,key,secret,return_url):
self.request = request
self.api = linkedin.LinkedIn(key,secret,return_url)
self.token = result = self.api.requestToken()
def login_url(self, next="/"):
return self.api.getAuthorizeURL(self.token)
def logout_url(self, next="/"):
return ''
def get_user(self):
result = self.request.vars.verifier and self.api.accessToken(verifier = self.request.vars.verifier )
if result:
profile = self.api.GetProfile()
profile = self.api.GetProfile(profile).public_url = "http://www.linkedin.com/in/ozgurv"
return dict(first_name = profile.first_name,
last_name = profile.last_name,
username = profile.id)
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Loginza.ru authentication for web2py
Developed by Vladimir Dronnikov (Copyright © 2011)
Email <dronnikov@gmail.com>
"""
import urllib
from gluon.html import *
from gluon.tools import fetch
from gluon.storage import Storage
import gluon.contrib.simplejson as json
class Loginza(object):
"""
from gluon.contrib.login_methods.loginza import Loginza
auth.settings.login_form = Loginza(request,
url = "http://localhost:8000/%s/default/user/login" % request.application)
"""
def __init__(self,
request,
url = "",
embed = True,
auth_url = "http://loginza.ru/api/authinfo",
language = "en",
prompt = "loginza",
on_login_failure = None,
):
self.request = request
self.token_url = url
self.embed = embed
self.auth_url = auth_url
self.language = language
self.prompt = prompt
self.profile = None
self.on_login_failure = on_login_failure
self.mappings = Storage()
# TODO: profile.photo is the URL to the picture
# Howto download and store it locally?
# FIXME: what if email is unique=True
self.mappings["http://twitter.com/"] = lambda profile:\
dict(registration_id = profile.get("identity",""),
username = profile.get("nickname",""),
email = profile.get("email",""),
last_name = profile.get("name","").get("full_name",""),
#avatar = profile.get("photo",""),
)
self.mappings["https://www.google.com/accounts/o8/ud"] = lambda profile:\
dict(registration_id = profile.get("identity",""),
username = profile.get("name","").get("full_name",""),
email = profile.get("email",""),
first_name = profile.get("name","").get("first_name",""),
last_name = profile.get("name","").get("last_name",""),
#avatar = profile.get("photo",""),
)
self.mappings["http://vkontakte.ru/"] = lambda profile:\
dict(registration_id=profile.get("identity",""),
username = profile.get("name","").get("full_name",""),
email = profile.get("email",""),
first_name = profile.get("name","").get("first_name",""),
last_name = profile.get("name","").get("last_name",""),
#avatar = profile.get("photo",""),
)
self.mappings.default = lambda profile:\
dict(registration_id = profile.get("identity",""),
username = profile.get("name","").get("full_name"),
email = profile.get("email",""),
first_name = profile.get("name","").get("first_name",""),
last_name = profile.get("name","").get("last_name",""),
#avatar = profile.get("photo",""),
)
def get_user(self):
request = self.request
if request.vars.token:
user = Storage()
data = urllib.urlencode(dict(token = request.vars.token))
auth_info_json = fetch(self.auth_url+'?'+data)
#print auth_info_json
auth_info = json.loads(auth_info_json)
if auth_info["identity"] != None:
self.profile = auth_info
provider = self.profile["provider"]
user = self.mappings.get(provider, self.mappings.default)(self.profile)
#user["password"] = ???
#user["avatar"] = ???
return user
elif self.on_login_failure:
redirect(self.on_login_failure)
return None
def login_form(self):
request = self.request
args = request.args
LOGINZA_URL = "https://loginza.ru/api/widget?lang=%s&token_url=%s&overlay=loginza"
if self.embed:
form = IFRAME(_src=LOGINZA_URL % (self.language, self.token_url),
_scrolling="no",
_frameborder="no",
_style="width:359px;height:300px;")
else:
form = DIV(A(self.prompt, _href=LOGINZA_URL % (self.language, self.token_url), _class="loginza"),
SCRIPT(_src="https://s3-eu-west-1.amazonaws.com/s1.loginza.ru/js/widget.js", _type="text/javascript"))
return form
@@ -0,0 +1,190 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Written by Michele Comitini <mcm@glisco.it>
License: GPL v3
Adds support for OAuth1.0a authentication to web2py.
Dependencies:
- python-oauth2 (http://github.com/simplegeo/python-oauth2)
"""
import oauth2 as oauth
import cgi
from urllib2 import urlopen
import urllib2
from urllib import urlencode
class OAuthAccount(object):
"""
Login will be done via OAuth Framework, instead of web2py's
login form.
Include in your model (eg db.py)::
# define the auth_table before call to auth.define_tables()
auth_table = db.define_table(
auth.settings.table_user_name,
Field('first_name', length=128, default=""),
Field('last_name', length=128, default=""),
Field('username', length=128, default="", unique=True),
Field('password', 'password', length=256,
readable=False, label='Password'),
Field('registration_key', length=128, default= "",
writable=False, readable=False))
auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username)
.
.
.
auth.define_tables()
.
.
.
CLIENT_ID=\"<put your fb application id here>\"
CLIENT_SECRET=\"<put your fb application secret here>\"
AUTH_URL="..."
TOKEN_URL="..."
ACCESS_TOKEN_URL="..."
from gluon.contrib.login_methods.oauth10a_account import OAuthAccount
auth.settings.login_form=OAuthAccount(globals(),CLIENT_ID,CLIENT_SECRET, AUTH_URL, TOKEN_URL, ACCESS_TOKEN_URL)
"""
def __redirect_uri(self, next=None):
"""Build the uri used by the authenticating server to redirect
the client back to the page originating the auth request.
Appends the _next action to the generated url so the flows continues.
"""
r = self.request
http_host=r.env.http_x_forwarded_for
if not http_host: http_host=r.env.http_host
url_scheme = r.env.wsgi_url_scheme
if next:
path_info = next
else:
path_info = r.env.path_info
uri = '%s://%s%s' %(url_scheme, http_host, path_info)
if r.get_vars and not next:
uri += '?' + urlencode(r.get_vars)
return uri
def accessToken(self):
"""Return the access token generated by the authenticating server.
If token is already in the session that one will be used.
Otherwise the token is fetched from the auth server.
"""
if self.session.access_token:
# return the token (TODO: does it expire?)
return self.session.access_token
if self.session.request_token:
# Exchange the request token with an authorization token.
token = self.session.request_token
self.session.request_token = None
# Build an authorized client
# OAuth1.0a put the verifier!
token.set_verifier(self.request.vars.oauth_verifier)
client = oauth.Client(self.consumer, token)
resp, content = client.request(self.access_token_url, "POST")
if str(resp['status']) != '200':
self.session.request_token = None
self.globals['redirect'](self.globals['URL'](f='user',args='logout'))
self.session.access_token = oauth.Token.from_string(content)
return self.session.access_token
self.session.access_token = None
return None
def __init__(self, g, client_id, client_secret, auth_url, token_url, access_token_url):
self.globals = g
self.client_id = client_id
self.client_secret = client_secret
self.code = None
self.request = g['request']
self.session = g['session']
self.auth_url = auth_url
self.token_url = token_url
self.access_token_url = access_token_url
# consumer init
self.consumer = oauth.Consumer(self.client_id, self.client_secret)
def login_url(self, next="/"):
self.__oauth_login(next)
return next
def logout_url(self, next="/"):
self.session.request_token = None
self.session.access_token = None
return next
def get_user(self):
'''Get user data.
Since OAuth does not specify what a user
is, this function must be implemented for the specific
provider.
'''
raise NotImplementedError, "Must override get_user()"
def __oauth_login(self, next):
'''This method redirects the user to the authenticating form
on authentication server if the authentication code
and the authentication token are not available to the
application yet.
Once the authentication code has been received this method is
called to set the access token into the session by calling
accessToken()
'''
if not self.accessToken():
# setup the client
client = oauth.Client(self.consumer, None)
# Get a request token.
# oauth_callback *is REQUIRED* for OAuth1.0a
# putting it in the body seems to work.
callback_url = self.__redirect_uri(next)
data = urlencode(dict(oauth_callback=callback_url))
resp, content = client.request(self.token_url, "POST", body=data)
if resp['status'] != '200':
self.session.request_token = None
self.globals['redirect'](self.globals['URL'](f='user',args='logout'))
# Store the request token in session.
request_token = self.session.request_token = oauth.Token.from_string(content)
# Redirect the user to the authentication URL and pass the callback url.
data = urlencode(dict(oauth_token=request_token.key,
oauth_callback=callback_url))
auth_request_url = self.auth_url + '?' +data
HTTP = self.globals['HTTP']
raise HTTP(307,
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
Location=auth_request_url)
return None
@@ -0,0 +1,207 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Written by Michele Comitini <mcm@glisco.it>
License: GPL v3
Adds support for OAuth 2.0 authentication to web2py.
OAuth 2.0 Draft: http://tools.ietf.org/html/draft-ietf-oauth-v2-10
"""
import time
import cgi
from urllib2 import urlopen
import urllib2
from urllib import urlencode
class OAuthAccount(object):
"""
Login will be done via OAuth Framework, instead of web2py's
login form.
Include in your model (eg db.py)::
# define the auth_table before call to auth.define_tables()
auth_table = db.define_table(
auth.settings.table_user_name,
Field('first_name', length=128, default=""),
Field('last_name', length=128, default=""),
Field('username', length=128, default="", unique=True),
Field('password', 'password', length=256,
readable=False, label='Password'),
Field('registration_key', length=128, default= "",
writable=False, readable=False))
auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username)
.
.
.
auth.define_tables()
.
.
.
CLIENT_ID=\"<put your fb application id here>\"
CLIENT_SECRET=\"<put your fb application secret here>\"
AUTH_URL="http://..."
TOKEN_URL="http://..."
from gluon.contrib.login_methods.oauth20_account import OAuthAccount
auth.settings.login_form=OAuthAccount(globals(),CLIENT_ID,CLIENT_SECRET,AUTH_URL, TOKEN_URL, **args )
Any optional arg will be passed as is to remote server for requests.
It can be used for the optional "scope" parameters for Facebook.
"""
def __redirect_uri(self, next=None):
"""Build the uri used by the authenticating server to redirect
the client back to the page originating the auth request.
Appends the _next action to the generated url so the flows continues.
"""
r = self.request
http_host=r.env.http_x_forwarded_for
if not http_host: http_host=r.env.http_host
url_scheme = r.env.wsgi_url_scheme
if next:
path_info = next
else:
path_info = r.env.path_info
uri = '%s://%s%s' %(url_scheme, http_host, path_info)
if r.get_vars and not next:
uri += '?' + urlencode(r.get_vars)
return uri
def __build_url_opener(self, uri):
"""Build the url opener for managing HTTP Basic Athentication"""
# Create an OpenerDirector with support for Basic HTTP Authentication...
auth_handler = urllib2.HTTPBasicAuthHandler()
auth_handler.add_password(None,
uri,
self.client_id,
self.client_secret)
opener = urllib2.build_opener(auth_handler)
return opener
def accessToken(self):
"""Return the access token generated by the authenticating server.
If token is already in the session that one will be used.
Otherwise the token is fetched from the auth server.
"""
if self.session.token and self.session.token.has_key('expires'):
expires = self.session.token['expires']
# reuse token until expiration
if expires == 0 or expires > time.time():
return self.session.token['access_token']
if self.session.code:
data = dict(client_id=self.client_id,
client_secret=self.client_secret,
redirect_uri=self.session.redirect_uri,
response_type='token', code=self.session.code)
if self.args:
data.update(self.args)
open_url = None
opener = self.__build_url_opener(self.token_url)
try:
open_url = opener.open(self.token_url, urlencode(data))
except urllib2.HTTPError, e:
raise Exception(e.read())
finally:
del self.session.code # throw it away
if open_url:
try:
tokendata = cgi.parse_qs(open_url.read())
self.session.token = dict([(k,v[-1]) for k,v in tokendata.items()])
# set expiration absolute time try to avoid broken
# implementations where "expires_in" becomes "expires"
if self.session.token.has_key('expires_in'):
exps = 'expires_in'
else:
exps = 'expires'
self.session.token['expires'] = int(self.session.token[exps]) + \
time.time()
finally:
opener.close()
return self.session.token['access_token']
self.session.token = None
return None
def __init__(self, g, client_id, client_secret, auth_url, token_url, **args):
self.globals = g
self.client_id = client_id
self.client_secret = client_secret
self.request = g['request']
self.session = g['session']
self.auth_url = auth_url
self.token_url = token_url
self.args = args
def login_url(self, next="/"):
self.__oauth_login(next)
return next
def logout_url(self, next="/"):
del self.session.token
return next
def get_user(self):
'''Returns the user using the Graph API.
'''
raise NotImplementedError, "Must override get_user()"
if not self.accessToken():
return None
if not self.graph:
self.graph = GraphAPI((self.accessToken()))
user = None
try:
user = self.graph.get_object("me")
except GraphAPIError:
self.session.token = None
self.graph = None
if user:
return dict(first_name = user['first_name'],
last_name = user['last_name'],
username = user['id'])
def __oauth_login(self, next):
'''This method redirects the user to the authenticating form
on authentication server if the authentication code
and the authentication token are not available to the
application yet.
Once the authentication code has been received this method is
called to set the access token into the session by calling
accessToken()
'''
if not self.accessToken():
if not self.request.vars.code:
self.session.redirect_uri=self.__redirect_uri(next)
data = dict(redirect_uri=self.session.redirect_uri,
response_type='code',
client_id=self.client_id)
if self.args:
data.update(self.args)
auth_request_url = self.auth_url + "?" +urlencode(data)
HTTP = self.globals['HTTP']
raise HTTP(307,
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
Location=auth_request_url)
else:
self.session.code = self.request.vars.code
self.accessToken()
return self.session.code
return None
+632
View File
@@ -0,0 +1,632 @@
#!/usr/bin/env python
# coding: utf8
"""
OpenID authentication for web2py
Allowed using OpenID login together with web2py built-in login.
By default, to support OpenID login, put this in your db.py
>>> from gluon.contrib.login_methods.openid_auth import OpenIDAuth
>>> auth.settings.login_form = OpenIDAuth(auth)
To show OpenID list in user profile, you can add the following code
before the end of function user() of your_app/controllers/default.py
+ if (request.args and request.args(0) == "profile"):
+ form = DIV(form, openid_login_form.list_user_openids())
return dict(form=form, login_form=login_form, register_form=register_form, self_registration=self_registration)
More detail in the description of the class OpenIDAuth.
Requirements:
python-openid version 2.2.5 or later
Reference:
* w2p openID
http://w2popenid.appspot.com/init/default/wiki/w2popenid
* RPX and web2py auth module
http://www.web2pyslices.com/main/slices/take_slice/28
* built-in file: gluon/contrib/login_methods/rpx_account.py
* built-in file: gluon/tools.py (Auth class)
"""
import time
from datetime import datetime, timedelta
from gluon import *
from gluon.storage import Storage, Messages
try:
import openid.consumer.consumer
from openid.association import Association
from openid.store.interface import OpenIDStore
from openid.extensions.sreg import SRegRequest, SRegResponse
from openid.store import nonce
from openid.consumer.discover import DiscoveryFailure
except ImportError, err:
raise ImportError("OpenIDAuth requires python-openid package")
DEFAULT = lambda: None
class OpenIDAuth(object):
"""
OpenIDAuth
It supports the logout_url, implementing the get_user and login_form
for cas usage of gluon.tools.Auth.
It also uses the ExtendedLoginForm to allow the OpenIDAuth login_methods
combined with the standard logon/register procedure.
It uses OpenID Consumer when render the form and begins the OpenID
authentication.
Example: (put these code after auth.define_tables() in your models.)
auth = Auth(globals(), db) # authentication/authorization
...
auth.define_tables() # creates all needed tables
...
#include in your model after auth has been defined
from gluon.contrib.login_methods.openid_auth import OpenIDAuth
openid_login_form = OpenIDAuth(request, auth, db)
from gluon.contrib.login_methods.extended_login_form import ExtendedLoginForm
extended_login_form = ExtendedLoginForm(request, auth, openid_login_form,
signals=['oid','janrain_nonce'])
auth.settings.login_form = extended_login_form
"""
def __init__(self, auth):
self.auth = auth
self.db = auth.db
request = current.request
self.nextvar = '_next'
self.realm = 'http://%s' % request.env.http_host
self.login_url = URL(r=request, f='user', args=['login'])
self.return_to_url = self.realm + self.login_url
self.table_alt_logins_name = "alt_logins"
if not auth.settings.table_user:
raise
self.table_user = self.auth.settings.table_user
self.openid_expiration = 15 #minutes
self.messages = self._define_messages()
if not self.table_alt_logins_name in self.db.tables:
self._define_alt_login_table()
def _define_messages(self):
messages = Messages(current.T)
messages.label_alt_login_username = 'Sign-in with OpenID: '
messages.label_add_alt_login_username = 'Add a new OpenID: '
messages.submit_button = 'Sign in'
messages.submit_button_add = 'Add'
messages.a_delete = 'Delete'
messages.comment_openid_signin = 'What is OpenID?'
messages.comment_openid_help_title = 'Start using your OpenID'
messages.comment_openid_help_url = 'http://openid.net/get-an-openid/start-using-your-openid/'
messages.openid_fail_discover = 'Failed to discover OpenID service. Check your OpenID or "More about OpenID"?'
messages.flash_openid_expired = 'OpenID expired. Please login or authenticate OpenID again. Sorry for the inconvenient.'
messages.flash_openid_associated = 'OpenID associated'
messages.flash_associate_openid = 'Please login or register an account for this OpenID.'
messages.p_openid_not_registered = "This Open ID haven't be registered. " \
+ "Please login to associate with it or register an account for it."
messages.flash_openid_authenticated = 'OpenID authenticated successfully.'
messages.flash_openid_fail_authentication = 'OpenID authentication failed. (Error message: %s)'
messages.flash_openid_canceled = 'OpenID authentication canceled by user.'
messages.flash_openid_need_setup = 'OpenID authentication needs to be setup by the user with the provider first.'
messages.h_openid_login = 'OpenID Login'
messages.h_openid_list = 'OpenID List'
return messages
def _define_alt_login_table(self):
"""
Define the OpenID login table.
Note: type is what I used for our project. We're going to support 'fackbook' and
'plurk' alternate login methods. Otherwise it's always 'openid' and you
may not need it. This should be easy to changed.
(Just remove the field of "type" and remove the
"and db.alt_logins.type == type_" in _find_matched_openid function)
"""
db = self.db
table = db.define_table(
self.table_alt_logins_name,
Field('username', length=512, default=''),
Field('type', length=128, default='openid', readable=False),
Field('user', self.table_user, readable=False),
)
table.username.requires = IS_NOT_IN_DB(db, table.username)
self.table_alt_logins = table
def logout_url(self, next):
"""
Delete the w2popenid record in session as logout
"""
if current.session.w2popenid:
del(current.session.w2popenid)
return next
def login_form(self):
"""
Start to process the OpenID response if 'janrain_nonce' in request parameters
and not processed yet. Else return the OpenID form for login.
"""
request = current.request
if request.vars.has_key('janrain_nonce') and not self._processed():
self._process_response()
return self.auth()
return self._form()
def get_user(self):
"""
It supports the logout_url, implementing the get_user and login_form
for cas usage of gluon.tools.Auth.
"""
request = current.request
args = request.args
if args[0] == 'logout':
return True # Let logout_url got called
if current.session.w2popenid:
w2popenid = current.session.w2popenid
db = self.db
if (w2popenid.ok is True and w2popenid.oid): # OpenID authenticated
if self._w2popenid_expired(w2popenid):
del(current.session.w2popenid)
flash = self.messages.flash_openid_expired
current.session.warning = flash
redirect(self.auth.settings.login_url)
oid = self._remove_protocol(w2popenid.oid)
alt_login = self._find_matched_openid(db, oid)
nextvar = self.nextvar
# This OpenID not in the database. If user logged in then add it
# into database, else ask user to login or register.
if not alt_login:
if self.auth.is_logged_in():
# TODO: ask first maybe
self._associate_user_openid(self.auth.user, oid)
if current.session.w2popenid:
del(current.session.w2popenid)
current.session.flash = self.messages.flash_openid_associated
if request.vars.has_key(nextvar):
redirect(request.vars[nextvar])
redirect(self.auth.settings.login_next)
if not request.vars.has_key(nextvar):
# no next var, add it and do login again
# so if user login or register can go back here to associate the OpenID
redirect(URL(r=request,
args=['login'],
vars={nextvar:self.login_url}))
self.login_form = self._form_with_notification()
current.session.flash = self.messages.flash_associate_openid
return None # need to login or register to associate this openid
# Get existed OpenID user
user = db(self.table_user.id==alt_login.user).select().first()
if user:
if current.session.w2popenid:
del(current.session.w2popenid)
if 'username' in self.table_user.fields():
username = 'username'
elif 'email' in self.table_user.fields():
username = 'email'
return {username: user[username]} if user else None # login success (almost)
return None # just start to login
def _find_matched_openid(self, db, oid, type_='openid'):
"""
Get the matched OpenID for given
"""
query = ((db.alt_logins.username == oid) & (db.alt_logins.type == type_))
alt_login = db(query).select().first() # Get the OpenID record
return alt_login
def _associate_user_openid(self, user, oid):
"""
Associate the user logged in with given OpenID
"""
# print "[DB] %s authenticated" % oid
self.db.alt_logins.insert(username=oid, user=user.id)
def _form_with_notification(self):
"""
Render the form for normal login with a notice of OpenID authenticated
"""
form = DIV()
# TODO: check when will happen
if self.auth.settings.login_form in (self.auth, self):
self.auth.settings.login_form = self.auth
form = DIV(self.auth())
register_note = DIV(P(self.messages.p_openid_not_registered))
form.components.append(register_note)
return lambda: form
def _remove_protocol(self, oid):
"""
Remove https:// or http:// from oid url
"""
protocol = 'https://'
if oid.startswith(protocol):
oid = oid[len(protocol):]
return oid
protocol = 'http://'
if oid.startswith(protocol):
oid = oid[len(protocol):]
return oid
return oid
def _init_consumerhelper(self):
"""
Initialize the ConsumerHelper
"""
if not hasattr(self, "consumerhelper"):
self.consumerhelper = ConsumerHelper(current.session,
self.db)
return self.consumerhelper
def _form(self, style=None):
form = DIV(H3(self.messages.h_openid_login), self._login_form(style))
return form
def _login_form(self,
openid_field_label=None,
submit_button=None,
_next=None,
style=None):
"""
Render the form for OpenID login
"""
def warning_openid_fail(session):
session.warning = messages.openid_fail_discover
style = style or """
background-attachment: scroll;
background-repeat: no-repeat;
background-image: url("http://wiki.openid.net/f/openid-16x16.gif");
background-position: 0% 50%;
background-color: transparent;
padding-left: 18px;
width: 400px;
"""
style = style.replace("\n","")
request = current.request
session = current.session
messages = self.messages
hidden_next_input = ""
if _next == 'profile':
profile_url = URL(r=request, f='user', args=['profile'])
hidden_next_input = INPUT(_type="hidden", _name="_next", _value=profile_url)
form = FORM(openid_field_label or self.messages.label_alt_login_username,
INPUT(_type="input", _name="oid",
requires=IS_NOT_EMPTY(error_message=messages.openid_fail_discover),
_style=style),
hidden_next_input,
INPUT(_type="submit", _value=submit_button or messages.submit_button),
" ",
A(messages.comment_openid_signin,
_href=messages.comment_openid_help_url,
_title=messages.comment_openid_help_title,
_class='openid-identifier',
_target="_blank"),
_action=self.login_url
)
if form.accepts(request.vars, session):
oid = request.vars.oid
consumerhelper = self._init_consumerhelper()
url = self.login_url
return_to_url = self.return_to_url
if not oid:
warning_openid_fail(session)
redirect(url)
try:
if request.vars.has_key('_next'):
return_to_url = self.return_to_url + '?_next=' + request.vars._next
url = consumerhelper.begin(oid, self.realm, return_to_url)
except DiscoveryFailure:
warning_openid_fail(session)
redirect(url)
return form
def _processed(self):
"""
Check if w2popenid authentication is processed.
Return True if processed else False.
"""
processed = (hasattr(current.session, 'w2popenid') and
current.session.w2popenid.ok is True)
return processed
def _set_w2popenid_expiration(self, w2popenid):
"""
Set expiration for OpenID authentication.
"""
w2popenid.expiration = datetime.now() + timedelta(minutes=self.openid_expiration)
def _w2popenid_expired(self, w2popenid):
"""
Check if w2popenid authentication is expired.
Return True if expired else False.
"""
return (not w2popenid.expiration) or (datetime.now() > w2popenid.expiration)
def _process_response(self):
"""
Process the OpenID by ConsumerHelper.
"""
request = current.request
request_vars = request.vars
consumerhelper = self._init_consumerhelper()
process_status = consumerhelper.process_response(request_vars, self.return_to_url)
if process_status == "success":
w2popenid = current.session.w2popenid
user_data = self.consumerhelper.sreg()
current.session.w2popenid.ok = True
self._set_w2popenid_expiration(w2popenid)
w2popenid.user_data = user_data
current.session.flash = self.messages.flash_openid_authenticated
elif process_status == "failure":
flash = self.messages.flash_openid_fail_authentication % consumerhelper.error_message
current.session.warning = flash
elif process_status == "cancel":
current.session.warning = self.messages.flash_openid_canceled
elif process_status == "setup_needed":
current.session.warning = self.messages.flash_openid_need_setup
def list_user_openids(self):
messages = self.messages
request = current.request
if request.vars.has_key('delete_openid'):
self.remove_openid(request.vars.delete_openid)
query = self.db.alt_logins.user == self.auth.user.id
alt_logins = self.db(query).select()
l = []
for alt_login in alt_logins:
username = alt_login.username
delete_href = URL(r=request, f='user',
args=['profile'],
vars={'delete_openid': username})
delete_link = A(messages.a_delete, _href=delete_href)
l.append(LI(username, " ", delete_link))
profile_url = URL(r=request, f='user', args=['profile'])
#return_to_url = self.return_to_url + '?' + self.nextvar + '=' + profile_url
openid_list = DIV(H3(messages.h_openid_list), UL(l),
self._login_form(
_next='profile',
submit_button=messages.submit_button_add,
openid_field_label=messages.label_add_alt_login_username)
)
return openid_list
def remove_openid(self, openid):
query = self.db.alt_logins.username == openid
self.db(query).delete()
class ConsumerHelper(object):
"""
ConsumerHelper knows the python-openid and
"""
def __init__(self, session, db):
self.session = session
store = self._init_store(db)
self.consumer = openid.consumer.consumer.Consumer(session, store)
def _init_store(self, db):
"""
Initialize Web2pyStore
"""
if not hasattr(self, "store"):
store = Web2pyStore(db)
session = self.session
if not session.has_key('w2popenid'):
session.w2popenid = Storage()
self.store = store
return self.store
def begin(self, oid, realm, return_to_url):
"""
Begin the OpenID authentication
"""
w2popenid = self.session.w2popenid
w2popenid.oid = oid
auth_req = self.consumer.begin(oid)
auth_req.addExtension(SRegRequest(required=['email','nickname']))
url = auth_req.redirectURL(return_to=return_to_url, realm=realm)
return url
def process_response(self, request_vars, return_to_url):
"""
Complete the process and
"""
resp = self.consumer.complete(request_vars, return_to_url)
if resp:
if resp.status == openid.consumer.consumer.SUCCESS:
self.resp = resp
if hasattr(resp, "identity_url"):
self.session.w2popenid.oid = resp.identity_url
return "success"
if resp.status == openid.consumer.consumer.FAILURE:
self.error_message = resp.message
return "failure"
if resp.status == openid.consumer.consumer.CANCEL:
return "cancel"
if resp.status == openid.consumer.consumer.SETUP_NEEDED:
return "setup_needed"
return "no resp"
def sreg(self):
"""
Try to get OpenID Simple Registation
http://openid.net/specs/openid-simple-registration-extension-1_0.html
"""
if self.resp:
resp = self.resp
sreg_resp = SRegResponse.fromSuccessResponse(resp)
return sreg_resp.data if sreg_resp else None
else:
return None
class Web2pyStore(OpenIDStore):
"""
Web2pyStore
This class implements the OpenIDStore interface. OpenID stores take care
of persisting nonces and associations. The Janrain Python OpenID library
comes with implementations for file and memory storage. Web2pyStore uses
the web2py db abstration layer. See the source code docs of OpenIDStore
for a comprehensive description of this interface.
"""
def __init__(self, database):
self.database = database
self.table_oid_associations_name = 'oid_associations'
self.table_oid_nonces_name = 'oid_nonces'
self._initDB()
def _initDB(self):
if self.table_oid_associations_name not in self.database:
self.database.define_table(self.table_oid_associations_name,
Field('server_url', 'string', length=2047, required=True),
Field('handle', 'string', length=255, required=True),
Field('secret', 'blob', required=True),
Field('issued', 'integer', required=True),
Field('lifetime', 'integer', required=True),
Field('assoc_type', 'string', length=64, required=True)
)
if self.table_oid_nonces_name not in self.database:
self.database.define_table(self.table_oid_nonces_name,
Field('server_url', 'string', length=2047, required=True),
Field('timestamp', 'integer', required=True),
Field('salt', 'string', length=40, required=True)
)
def storeAssociation(self, server_url, association):
"""
Store associations. If there already is one with the same
server_url and handle in the table replace it.
"""
db = self.database
query = (db.oid_associations.server_url == server_url) & (db.oid_associations.handle == association.handle)
db(query).delete()
db.oid_associations.insert(server_url = server_url,
handle = association.handle,
secret = association.secret,
issued = association.issued,
lifetime = association.lifetime,
assoc_type = association.assoc_type), 'insert '*10
def getAssociation(self, server_url, handle=None):
"""
Return the association for server_url and handle. If handle is
not None return the latests associations for that server_url.
Return None if no association can be found.
"""
db = self.database
query = (db.oid_associations.server_url == server_url)
if handle:
query &= (db.oid_associations.handle == handle)
rows = db(query).select(orderby=db.oid_associations.issued)
keep_assoc, _ = self._removeExpiredAssocations(rows)
if len(keep_assoc) == 0:
return None
else:
assoc = keep_assoc.pop() # pop the last one as it should be the latest one
return Association(assoc['handle'],
assoc['secret'],
assoc['issued'],
assoc['lifetime'],
assoc['assoc_type'])
def removeAssociation(self, server_url, handle):
db = self.database
query = (db.oid_associations.server_url == server_url) & (db.oid_associations.handle == handle)
return db(query).delete() != None
def useNonce(self, server_url, timestamp, salt):
"""
This method returns Falase if a nonce has been used before or its
timestamp is not current.
"""
db = self.database
if abs(timestamp - time.time()) > nonce.SKEW:
return False
query = (db.oid_nonces.server_url == server_url) & (db.oid_nonces.timestamp == timestamp) & (db.oid_nonces.salt == salt)
if db(query).count() > 0:
return False
else:
db.oid_nonces.insert(server_url = server_url,
timestamp = timestamp,
salt = salt)
return True
def _removeExpiredAssocations(self, rows):
"""
This helper function is not part of the interface. Given a list of
association rows it checks which associations have expired and
deletes them from the db. It returns a tuple of the form
([valid_assoc], no_of_expired_assoc_deleted).
"""
db = self.database
keep_assoc = []
remove_assoc = []
t1970 = time.time()
for r in rows:
if r['issued'] + r['lifetime'] < t1970:
remove_assoc.append(r)
else:
keep_assoc.append(r)
for r in remove_assoc:
del db.oid_associations[r['id']]
return (keep_assoc, len(remove_assoc)) # return tuple (list of valid associations, number of deleted associations)
def cleanupNonces(self):
"""
Remove expired nonce entries from DB and return the number
of entries deleted.
"""
db = self.database
query = (db.oid_nonces.timestamp < time.time() - nonce.SKEW)
return db(query).delete()
def cleanupAssociations(self):
"""
Remove expired associations from db and return the number
of entries deleted.
"""
db = self.database
query = (db.oid_associations.id > 0)
return self._removeExpiredAssocations(db(query).select())[1] #return number of assoc removed
def cleanup(self):
"""
This method should be run periodically to free the db from
expired nonce and association entries.
"""
return self.cleanupNonces(), self.cleanupAssociations()
+14
View File
@@ -0,0 +1,14 @@
from gluon.contrib.pam import authenticate
def pam_auth():
"""
to use pam_login:
from gluon.contrib.login_methods.pam_auth import pam_auth
auth.settings.login_methods.append(pam_auth())
"""
def pam_auth_aux(username, password):
return authenticate(username, password)
return pam_auth_aux
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python
# coding: utf8
"""
RPX Authentication for web2py
Developed by Nathan Freeze (Copyright © 2009)
Email <nathan@freezable.com>
Modified by Massimo Di Pierro
This file contains code to allow using RPXNow.com (now Jainrain.com)
services with web2py
"""
import os
import re
import urllib
from gluon import *
from gluon.tools import fetch
from gluon.storage import Storage
import gluon.contrib.simplejson as json
class RPXAccount(object):
"""
from gluon.contrib.login_methods.rpx_account import RPXAccount
auth.settings.actions_disabled=['register','change_password','request_reset_password']
auth.settings.login_form = RPXAccount(request,
api_key="...",
domain="...",
url = "http://localhost:8000/%s/default/user/login" % request.application)
"""
def __init__(self,
request,
api_key = "",
domain = "",
url = "",
embed = True,
auth_url = "https://rpxnow.com/api/v2/auth_info",
language= "en",
prompt='rpx',
on_login_failure = None,
):
self.request=request
self.api_key=api_key
self.embed = embed
self.auth_url = auth_url
self.domain = domain
self.token_url = url
self.language = language
self.profile = None
self.prompt = prompt
self.on_login_failure = on_login_failure
self.mappings = Storage()
dn = {'givenName':'','familyName':''}
self.mappings.Facebook = lambda profile, dn=dn:\
dict(registration_id = profile.get("identifier",""),
username = profile.get("preferredUsername",""),
email = profile.get("email",""),
first_name = profile.get("name",dn).get("givenName",""),
last_name = profile.get("name",dn).get("familyName",""))
self.mappings.Google = lambda profile, dn=dn:\
dict(registration_id=profile.get("identifier",""),
username=profile.get("preferredUsername",""),
email=profile.get("email",""),
first_name=profile.get("name",dn).get("givenName",""),
last_name=profile.get("name",dn).get("familyName",""))
self.mappings.default = lambda profile:\
dict(registration_id=profile.get("identifier",""),
username=profile.get("preferredUsername",""),
email=profile.get("email",""),
first_name=profile.get("preferredUsername",""),
last_name='')
def get_user(self):
request = self.request
if request.vars.token:
user = Storage()
data = urllib.urlencode(dict(apiKey = self.api_key, token=request.vars.token))
auth_info_json = fetch(self.auth_url+'?'+data)
auth_info = json.loads(auth_info_json)
if auth_info['stat'] == 'ok':
self.profile = auth_info['profile']
provider = re.sub('[^\w\-]','',self.profile['providerName'])
user = self.mappings.get(provider,self.mappings.default)(self.profile)
return user
elif self.on_login_failure:
redirect(self.on_login_failure)
return None
def login_form(self):
request = self.request
args = request.args
if self.embed:
JANRAIN_URL = \
"https://%s.rpxnow.com/openid/embed?token_url=%s&language_preference=%s"
rpxform = IFRAME(_src=JANRAIN_URL % (self.domain,self.token_url,self.language),
_scrolling="no",
_frameborder="no",
_style="width:400px;height:240px;")
else:
JANRAIN_URL = \
"https://%s.rpxnow.com/openid/v2/signin?token_url=%s"
rpxform = DIV(SCRIPT(_src="https://rpxnow.com/openid/v2/widget",
_type="text/javascript"),
SCRIPT("RPXNOW.overlay = true;",
"RPXNOW.language_preference = '%s';" % self.language,
"RPXNOW.realm = '%s';" % self.domain,
"RPXNOW.token_url = '%s';" % self.token_url,
"RPXNOW.show();",
_type="text/javascript"))
return rpxform
def use_janrain(auth,filename='private/janrain.key',**kwargs):
path = os.path.join(current.request.folder,filename)
if os.path.exists(path):
request = current.request
domain,key = open(path,'r').read().strip().split(':')
host = current.request.env.http_host
url = "http://%s/%s/default/user/login" % (host,request.application)
auth.settings.actions_disabled = \
['register','change_password','request_reset_password']
auth.settings.login_form = RPXAccount(
request, api_key=key,domain=domain, url = url,**kwargs)
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Written by Michele Comitini <mcm@glisco.it>
License: GPL v3
Adds support for x509 authentication.
"""
from gluon.globals import current
from gluon.storage import Storage
from gluon.http import HTTP,redirect
#requires M2Crypto
from M2Crypto import X509
class X509Auth(object):
"""
Login using x509 cert from client.
from gluon.contrib.login_methods.x509_auth import X509Account
auth.settings.actions_disabled=['register','change_password',
'request_reset_password','profile']
auth.settings.login_form = X509Account()
"""
def __init__(self):
self.request = current.request
self.ssl_client_raw_cert = self.request.env.ssl_client_raw_cert
# rebuild the certificate passed by the env
# this is double work, but it is the only way
# since we cannot access the web server ssl engine directly
if self.ssl_client_raw_cert:
x509=X509.load_cert_string(self.ssl_client_raw_cert, X509.FORMAT_PEM)
# extract it from the cert
self.serial = self.request.env.ssl_client_serial or ('%x' % x509.get_serial_number()).upper()
subject = x509.get_subject()
# Reordering the subject map to a usable Storage map
# this allows us a cleaner syntax:
# cn = self.subject.cn
self.subject = Storage(filter(None,
map(lambda x:
(x,map(lambda y:
y.get_data().as_text(),
subject.get_entries_by_nid(subject.nid[x]))),
subject.nid.keys())))
def login_form(self, **args):
raise HTTP(403,'Login not allowed. No valid x509 crentials')
def login_url(self, next="/"):
raise HTTP(403,'Login not allowed. No valid x509 crentials')
def logout_url(self, next="/"):
return next
def get_user(self):
'''Returns the user info contained in the certificate.
'''
# We did not get the client cert?
if not self.ssl_client_raw_cert:
return None
# Try to reconstruct some useful info for web2py auth machinery
p = profile = dict()
username = p['username'] = self.subject.CN or self.subject.commonName
p['first_name'] = self.subject.givenName or username
p['last_name'] = self.subject.surname
p['email'] = self.subject.Email or self.subject.emailAddress
# IMPORTANT WE USE THE CERT SERIAL AS UNIQUE KEY FOR THE USER
p['registration_id'] = self.serial
# If the auth table has a field certificate it will be used to
# save a PEM encoded copy of the user certificate.
p['certificate'] = self.ssl_client_raw_cert
return profile
+1
View File
@@ -0,0 +1 @@
markdown2.py is released under MIT license.
+17
View File
@@ -0,0 +1,17 @@
from markdown2 import *
from gluon.html import XML
def WIKI(text, encoding="utf8", safe_mode='escape', html4tags=False, **attributes):
if not text:
test = ''
if attributes.has_key('extras'):
extras = attributes['extras']
del attributes['extras']
else:
extras=None
text = text.decode(encoding,'replace')
return XML(markdown(text,extras=extras,
safe_mode=safe_mode, html4tags=html4tags)\
.encode(encoding,'xmlcharrefreplace'),**attributes)
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
+35
View File
@@ -0,0 +1,35 @@
<html><body><h1>Markmin markup language</h1><h2>About</h2><p>This is a new markup language that we call markmin designed to produce high quality scientific papers and books and also put them online. We provide serializers for html, latex and pdf. It is implemented in the <code class="">markmin2html</code> function in the <code class="">markmin2html.py</code>.</p><p>Example of usage:</p><pre><code class="">&gt;&gt;&gt; m = "Hello **world** [[link http://web2py.com]]"
&gt;&gt;&gt; from markmin2html import markmin2html
&gt;&gt;&gt; print markmin2html(m)
&gt;&gt;&gt; from markmin2latex import markmin2latex
&gt;&gt;&gt; print markmin2latex(m)
&gt;&gt;&gt; from markmin2pdf import markmin2pdf # requires pdflatex
&gt;&gt;&gt; print markmin2pdf(m)</code></pre><h2>Why?</h2><p>We wanted a markup language with the following requirements:</p><ul><li>less than 100 lines of functional code</li><li>easy to read</li><li>secure</li><li>support table, ul, ol, code</li><li>support html5 video and audio elements (html serialization only)</li><li>can align images and resize them</li><li>can specify class for tables and code elements</li><li>can add anchors</li><li>does not use _ for markup (since it creates odd behavior)</li><li>automatically links urls</li><li>fast</li><li>easy to extend</li><li>supports latex and pdf including references</li><li>allows to describe the markup in the markup (this document is generated from markmin syntax)</li></ul><p>(results depend on text but in average for text ~100K markmin is 30% faster than markdown, for text ~10K it is 10x faster)</p><p>The <a href="http://www.lulu.com/product/paperback/web2py-%283rd-edition%29/12822827">web2py book</a> published by lulu, for example, was entirely generated with markmin2pdf from the online <a href="http://www.web2py.com/book">web2py wiki</a></p><h2>Download</h2><ul><li>http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2html.py</li><li>http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2latex.py</li><li>http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2pdf.py</li></ul><p>markmin2html.py and markmin2latex.py are single files and have no web2py dependence. Their license is BSD.</p><h2>Examples</h2><h3>Bold, italic, code and links</h3><table class=""><tr><td><b>SOURCE</b> </td><td><b>OUTPUT</b></td></tr><tr><td><code class=""># title</code> </td><td><b>title</b></td></tr><tr><td><code class="">## section</code> </td><td><b>section</b></td></tr><tr><td><code class="">### subsection</code> </td><td><b>subsection</b></td></tr><tr><td><code class="">**bold**</code> </td><td><b>bold</b></td></tr><tr><td><code class="">''italic''</code> </td><td><i>italic</i></td></tr><tr><td><code class="">``verbatim``</code> </td><td><code class="">verbatim</code></td></tr><tr><td><code class="">http://google.com</code> </td><td>http://google.com</td></tr><tr><td><code class="">[[click me #myanchor]]</code></td><td><a href="#myanchor">click me</a></td></tr></table>
<h3>More on links</h3><p>The format is always <code class="">[[title link]]</code>. Notice you can nest bold, italic and code inside the link title.</p><h3>Anchors <span id="myanchor"><span></h3><p>You can place an anchor anywhere in the text using the syntax <code class="">[[name]]</code> where <i>name</i> is the name of the anchor.
You can then link the anchor with <a href="#myanchor">link</a>, i.e. <code class="">[[link #myanchor]]</code>.</p><h3>Images</h3><p><img src="http://www.web2py.com/examples/static/web2py_logo.png" alt="some image" align="right" width="200px" />
This paragraph has an image aligned to the right with a width of 200px. Its is placed using the code</p><p><code class="">[[some image http://www.web2py.com/examples/static/web2py_logo.png right 200px]]</code>.</p><h3>Unordered Lists</h3><pre><code class="">- Dog
- Cat
- Mouse</code></pre><p>is rendered as</p><ul><li>Dog</li><li>Cat</li><li>Mouse</li></ul><p>Two new lines between items break the list in two lists.</p><h3>Ordered Lists</h3><pre><code class="">+ Dog
+ Cat
+ Mouse</code></pre><p>is rendered as</p><ol><li>Dog</li><li>Cat</li><li>Mouse</li></ol><h3>Tables</h3><p>Something like this
<pre><code class="">---------
**A** | **B** | **C**
0 | 0 | X
0 | X | 0
X | 0 | 0
-----:abc</code></pre>
is a table and is rendered as
<table class="abc"><tr><td><b>A</b></td><td><b>B</b></td><td><b>C</b></td></tr><tr><td>0</td><td>0</td><td>X</td></tr><tr><td>0</td><td>X</td><td>0</td></tr><tr><td>X</td><td>0</td><td>0</td></tr></table>Four or more dashes delimit the table and | separates the columns.
The <code class="">:abc</code> at the end sets the class for the table and it is optional.</p><h3>Blockquote</h3><p>A table with a single cell is rendered as a blockquote:</p><blockquote class="">Hello world</blockquote>
<h3>Code, <code class="">&lt;code&gt;</code>, escaping and extra stuff</h3><pre><code class="python">def test():
return "this is Python code"</code></pre><p>Optionally a ` inside a <code class="">``...``</code> block can be inserted escaped with !`!.
The <code class="">:python</code> after the markup is also optional. If present, by default, it is used to set the class of the &lt;code&gt; block.
The behavior can be overridden by passing an argument <code class="">extra</code> to the <code class="">render</code> function. For example:</p><pre><code class="python">&gt;&gt;&gt; markmin2html("``aaa``:custom",
extra=dict(custom=lambda text: 'x'+text+'x'))</code></pre><p>generates</p><code class="python">'xaaax'</code><p>(the <code class="">``...``:custom</code> block is rendered by the <code class="">custom=lambda</code> function passed to <code class="">render</code>).</p><h3>Html5 support</h3><p>Markmin also supports the &lt;video&gt; and &lt;audio&gt; html5 tags using the notation:
<pre><code class="">[[title link video]]
[[title link audio]]</code></pre></p><h3>Latex</h3><p>Formulas can be embedded into HTML with <code class="">$</code><code class="">$</code>formula<code class="">$</code><code class="">$</code>.
You can use Google charts to render the formula:</p><pre><code class="">&gt;&gt;&gt; LATEX = '&lt;img src="http://chart.apis.google.com/chart?cht=tx&amp;chl=%s" align="center"/&gt;'
&gt;&gt;&gt; markmin2html(text,{'latex':lambda code: LATEX % code.replace('"','"')})</code></pre><h3>Citations and References</h3><p>Citations are treated as internal links in html and proper citations in latex if there is a final section called "References". Items like</p><pre><code class="">- [[key]] value</code></pre><p>in the References will be translated into Latex</p><pre><code class="">\bibitem{key} value</code></pre><p>Here is an example of usage:</p><pre><code class="">As shown in Ref.``mdipierro``:cite
## References
- [[mdipierro]] web2py Manual, 3rd Edition, lulu.com</code></pre><h3>Caveats</h3><p><code class="">&lt;ul/&gt;</code>, <code class="">&lt;ol/&gt;</code>, <code class="">&lt;code/&gt;</code>, <code class="">&lt;table/&gt;</code>, <code class="">&lt;blockquote/&gt;</code>, <code class="">&lt;h1/&gt;</code>, ..., <code class="">&lt;h6/&gt;</code> do not have <code class="">&lt;p&gt;...&lt;/p&gt;</code> around them.</p></body></html>
Binary file not shown.
+463
View File
@@ -0,0 +1,463 @@
#!/usr/bin/env python
# created my Massimo Di Pierro
# license MIT/BSD/GPL
import re
import cgi
__all__ = ['render', 'markmin2html']
__doc__ = """
# Markmin markup language
## About
This is a new markup language that we call markmin designed to produce high quality scientific papers and books and also put them online. We provide serializers for html, latex and pdf. It is implemented in the ``markmin2html`` function in the ``markmin2html.py``.
Example of usage:
``
m = "Hello **world** [[link http://web2py.com]]"
from markmin2html import markmin2html
print markmin2html(m)
from markmin2latex import markmin2latex
print markmin2latex(m)
from markmin2pdf import markmin2pdf # requires pdflatex
print markmin2pdf(m)
``
## Why?
We wanted a markup language with the following requirements:
- less than 100 lines of functional code
- easy to read
- secure
- support table, ul, ol, code
- support html5 video and audio elements (html serialization only)
- can align images and resize them
- can specify class for tables and code elements
- can add anchors
- does not use _ for markup (since it creates odd behavior)
- automatically links urls
- fast
- easy to extend
- supports latex and pdf including references
- allows to describe the markup in the markup (this document is generated from markmin syntax)
(results depend on text but in average for text ~100K markmin is 30% faster than markdown, for text ~10K it is 10x faster)
The [[web2py book http://www.lulu.com/product/paperback/web2py-%283rd-edition%29/12822827]] published by lulu, for example, was entirely generated with markmin2pdf from the online [[web2py wiki http://www.web2py.com/book]]
## Download
- http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2html.py
- http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2latex.py
- http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2pdf.py
markmin2html.py and markmin2latex.py are single files and have no web2py dependence. Their license is BSD.
## Examples
### Bold, italic, code and links
--------------------------------------------------
**SOURCE** | **OUTPUT**
``# title`` | **title**
``## section`` | **section**
``### subsection`` | **subsection**
``**bold**`` | **bold**
``''italic''`` | ''italic''
``!`!`verbatim`!`!`` | ``verbatim``
``http://google.com`` | http://google.com
``[[click me #myanchor]]`` | [[click me #myanchor]]
---------------------------------------------------
### More on links
The format is always ``[[title link]]``. Notice you can nest bold, italic and code inside the link title.
### Anchors [[myanchor]]
You can place an anchor anywhere in the text using the syntax ``[[name]]`` where ''name'' is the name of the anchor.
You can then link the anchor with [[link #myanchor]], i.e. ``[[link #myanchor]]``.
### Images
[[some image http://www.web2py.com/examples/static/web2py_logo.png right 200px]]
This paragraph has an image aligned to the right with a width of 200px. Its is placed using the code
``[[some image http://www.web2py.com/examples/static/web2py_logo.png right 200px]]``.
### Unordered Lists
``
- Dog
- Cat
- Mouse
``
is rendered as
- Dog
- Cat
- Mouse
Two new lines between items break the list in two lists.
### Ordered Lists
``
+ Dog
+ Cat
+ Mouse
``
is rendered as
+ Dog
+ Cat
+ Mouse
### Tables
Something like this
``
---------
**A** | **B** | **C**
0 | 0 | X
0 | X | 0
X | 0 | 0
-----:abc
``
is a table and is rendered as
---------
**A** | **B** | **C**
0 | 0 | X
0 | X | 0
X | 0 | 0
-----:abc
Four or more dashes delimit the table and | separates the columns.
The ``:abc`` at the end sets the class for the table and it is optional.
### Blockquote
A table with a single cell is rendered as a blockquote:
-----
Hello world
-----
### Code, ``<code>``, escaping and extra stuff
``
def test():
return "this is Python code"
``:python
Optionally a ` inside a ``!`!`...`!`!`` block can be inserted escaped with !`!.
The ``:python`` after the markup is also optional. If present, by default, it is used to set the class of the <code> block.
The behavior can be overridden by passing an argument ``extra`` to the ``render`` function. For example:
``
markmin2html("!`!!`!aaa!`!!`!:custom",
extra=dict(custom=lambda text: 'x'+text+'x'))
``:python
generates
``'xaaax'``:python
(the ``!`!`...`!`!:custom`` block is rendered by the ``custom=lambda`` function passed to ``render``).
### Html5 support
Markmin also supports the <video> and <audio> html5 tags using the notation:
``
[[title link video]]
[[title link audio]]
``
### Latex and other extensions
Formulas can be embedded into HTML with ``$````$``formula``$````$``.
You can use Google charts to render the formula:
``
LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />'
markmin2html(text,{'latex':lambda code: LATEX % code.replace('"','\"')})
``
### Code with syntax highlighting
This requires a syntax highlighting tool, such as the web2py CODE helper.
``
extra={'code_cpp':lambda text: CODE(text,language='cpp').xml(),
'code_java':lambda text: CODE(text,language='java').xml(),
'code_python':lambda text: CODE(text,language='python').xml(),
'code_html':lambda text: CODE(text,language='html').xml()}
markmin2html(text,extra=extra)
``
Code can now be marked up as in this example:
``
!`!`
<html><body>example</body></html>
!`!`:code_html
``
### Citations and References
Citations are treated as internal links in html and proper citations in latex if there is a final section called "References". Items like
``
- [[key]] value
``
in the References will be translated into Latex
``
\\bibitem{key} value
``
Here is an example of usage:
``
As shown in Ref.!`!`mdipierro`!`!:cite
## References
- [[mdipierro]] web2py Manual, 3rd Edition, lulu.com
``
### Caveats
``<ul/>``, ``<ol/>``, ``<code/>``, ``<table/>``, ``<blockquote/>``, ``<h1/>``, ..., ``<h6/>`` do not have ``<p>...</p>`` around them.
"""
META = 'META'
LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />'
regex_newlines = re.compile('(\n\r)|(\r\n)')
regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S)
regex_maps = [
(re.compile('[ \t\r]+\n'),'\n'),
(re.compile('[ \t\r]+\n'),'\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'<b>\g<t></b>'),
(re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"),'<i>\g<t></i>'),
(re.compile('^#{6} (?P<t>[^\n]+)',re.M),'\n\n<<h6>\g<t></h6>\n'),
(re.compile('^#{5} (?P<t>[^\n]+)',re.M),'\n\n<<h5>\g<t></h5>\n'),
(re.compile('^#{4} (?P<t>[^\n]+)',re.M),'\n\n<<h4>\g<t></h4>\n'),
(re.compile('^#{3} (?P<t>[^\n]+)',re.M),'\n\n<<h3>\g<t></h3>\n'),
(re.compile('^#{2} (?P<t>[^\n]+)',re.M),'\n\n<<h2>\g<t></h2>\n'),
(re.compile('^#{1} (?P<t>[^\n]+)',re.M),'\n\n<<h1>\g<t></h1>\n'),
(re.compile('^\- +(?P<t>.*)',re.M),'<<ul><li>\g<t></li></ul>'),
(re.compile('^\+ +(?P<t>.*)',re.M),'<<ol><li>\g<t></li></ol>'),
(re.compile('</ol>\n<<ol>'),''),
(re.compile('</ul>\n<<ul>'),''),
(re.compile('<<'),'\n\n<<'),
(re.compile('\n\s+\n'),'\n\n')]
regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n',re.M|re.S)
regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]')
regex_image_center_width = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +center +(?P<w>\d+px)\]\]')
regex_image_width = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right) +(?P<w>\d+px)\]\]')
regex_image_center = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +center\]\]')
regex_image = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]')
regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]')
regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]')
regex_link = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+)\]\]')
regex_link_popup = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) popup\]\]')
regex_link_no_anchor = re.compile('\[\[ +(?P<k>\S+)\]\]')
regex_auto = re.compile('(?<!["\w\>])(?P<k>\w+://[\w\.\-\+\?&%\/]+)',re.M)
def render(text,extra={},allowed={},sep='p'):
"""
Arguments:
- text is the text to be processed
- extra is a dict like extra=dict(custom=lambda value: value) that process custom code
as in " ``this is custom code``:custom "
- allowed is a dictionary of list of allowed classes like
allowed = dict(code=('python','cpp','java'))
- sep can be 'p' to separate text in <p>...</p>
or can be 'br' to separate text using <br />
>>> render('this is\\n# a section\\nparagraph')
'<p>this is</p><h1>a section</h1><p>paragraph</p>'
>>> render('this is\\n## a subsection\\nparagraph')
'<p>this is</p><h2>a subsection</h2><p>paragraph</p>'
>>> render('this is\\n### a subsubsection\\nparagraph')
'<p>this is</p><h3>a subsubsection</h3><p>paragraph</p>'
>>> render('**hello world**')
'<p><b>hello world</b></p>'
>>> render('``hello world``')
'<code class="">hello world</code>'
>>> render('``hello world``:python')
'<code class="python">hello world</code>'
>>> render('``\\nhello\\nworld\\n``:python')
'<pre><code class="python">hello\\nworld</code></pre>'
>>> render("''hello world''")
'<p><i>hello world</i></p>'
>>> render('** hello** **world**')
'<p>** hello** <b>world</b></p>'
>>> render('- this\\n- is\\n- a list\\n\\nand this\\n- is\\n- another')
'<ul><li>this</li><li>is</li><li>a list</li></ul><p>and this</p><ul><li>is</li><li>another</li></ul>'
>>> render('+ this\\n+ is\\n+ a list\\n\\nand this\\n+ is\\n+ another')
'<ol><li>this</li><li>is</li><li>a list</li></ol><p>and this</p><ol><li>is</li><li>another</li></ol>'
>>> render("----\\na | b\\nc | d\\n----\\n")
'<table class=""><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>'
>>> render("----\\nhello world\\n----\\n")
'<blockquote class="">hello world</blockquote>'
>>> render('[[this is a link http://example.com]]')
'<p><a href="http://example.com">this is a link</a></p>'
>>> render('[[this is an image http://example.com left]]')
'<p><img src="http://example.com" alt="this is an image" align="left" /></p>'
>>> render('[[this is an image http://example.com left 200px]]')
'<p><img src="http://example.com" alt="this is an image" align="left" width="200px" /></p>'
>>> render('[[this is an image http://example.com video]]')
'<p><video src="http://example.com" controls></video></p>'
>>> render('[[this is an image http://example.com audio]]')
'<p><audio src="http://example.com" controls></audio></p>'
>>> render('[[this is a **link** http://example.com]]')
'<p><a href="http://example.com">this is a <b>link</b></a></p>'
>>> render("``aaa``:custom",extra=dict(custom=lambda text: 'x'+text+'x'))
'xaaax'
>>> print render(r"$$\int_a^b sin(x)dx$$")
<img src="http://chart.apis.google.com/chart?cht=tx&chl=\\int_a^b sin(x)dx" />
"""
text = str(text or '')
#############################################################
# replace all blocks marked with ``...``:class with META
# store them into segments they will be treated as code
#############################################################
segments, i = [], 0
text = regex_dd.sub('``\g<latex>``:latex ',text)
text = regex_newlines.sub('\n',text)
while True:
item = regex_code.search(text,i)
if not item: break
if item.group()==META:
segments.append((None,None))
text = text[:item.start()]+META+text[item.end():]
else:
c = item.group('c') or ''
if 'code' in allowed and not c in allowed['code']: c = ''
code = item.group('t').replace('!`!','`')
segments.append((code,c))
text = text[:item.start()]+META+text[item.end():]
i=item.start()+3
#############################################################
# do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces
#############################################################
text = '\n'.join(t.strip() for t in text.split('\n'))
text = cgi.escape(text)
for regex, sub in regex_maps:
text = regex.sub(sub,text)
#############################################################
# process tables and blockquotes
#############################################################
while True:
item = regex_table.search(text)
if not item: break
c = item.group('c') or ''
if 'table' in allowed and not c in allowed['table']: c = ''
content = item.group('t')
if ' | ' in content:
rows = content.replace('\n','</td></tr><tr><td>').replace(' | ','</td><td>')
text = text[:item.start()] + '<<table class="%s"><tr><td>'%c + rows + '</td></tr></table>' + text[item.end():]
else:
text = text[:item.start()] + '<<blockquote class="%s">'%c + content + '</blockquote>' + text[item.end():]
#############################################################
# deal with images, videos, audios and links
#############################################################
text = regex_anchor.sub('<span id="\g<t>"><span>', text)
text = regex_image_center_width.sub('<p align="center"><img src="\g<k>" alt="\g<t>" width="\g<w>" /></p>', text)
text = regex_image_width.sub('<img src="\g<k>" alt="\g<t>" align="\g<p>" width="\g<w>" />', text)
text = regex_image_center.sub('<p align="center"><img src="\g<k>" alt="\g<t>" /></p>', text)
text = regex_image.sub('<img src="\g<k>" alt="\g<t>" align="\g<p>" />', text)
text = regex_video.sub('<video src="\g<k>" controls></video>', text)
text = regex_audio.sub('<audio src="\g<k>" controls></audio>', text)
text = regex_link_popup.sub('<a href="\g<k>" target="_blank">\g<t></a>', text)
text = regex_link_no_anchor.sub('<a href="\g<k>">\g<k></a>', text)
text = regex_link.sub('<a href="\g<k>">\g<t></a>', text)
text = regex_auto.sub('<a href="\g<k>">\g<k></a>', text)
#############################################################
# deal with paragraphs (trick <<ul, <<ol, <<table, <<h1, etc)
# the << indicates that there should NOT be a new paragraph
# META indicates a code block therefore no new paragraph
#############################################################
items = [item.strip() for item in text.split('\n\n')]
if sep=='p':
text = ''.join(
(p[:2]!='<<' and p!=META and '<p>%s</p>'%p or '%s'%p) \
for p in items if p.strip())
elif sep=='br':
text = '<br />'.join(items)
#############################################################
# finally get rid of <<
#############################################################
text=text.replace('<<','<')
#############################################################
# process all code text
#############################################################
parts = text.split(META)
text = parts[0]
for i,(code,b) in enumerate(segments):
if code==None:
html = META
else:
if b in extra:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
html = extra[b](code)
elif b=='cite':
html = '['+','.join('<a href="#%s" class="%s">%s</a>' \
% (d,b,d) \
for d in cgi.escape(code).split(','))+']'
elif b=='latex':
html = LATEX % code.replace('"','\"').replace('\n',' ')
elif code[:1]=='\n' or code[-1:]=='\n':
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
html = '<pre><code class="%s">%s</code></pre>' % (b,cgi.escape(code))
else:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
html = '<code class="%s">%s</code>' % (b,cgi.escape(code))
text = text+html+parts[i+1]
return text
def markmin2html(text,extra={},allowed={},sep='p'):
return render(text,extra,allowed,sep)
if __name__ == '__main__':
import sys
import doctest
if sys.argv[1:2]==['-h']:
print '<html><body>'+markmin2html(__doc__)+'</body></html>'
elif len(sys.argv)>1:
fargv = open(sys.argv[1],'r')
try:
print '<html><body>'+markmin2html(fargv.read())+'</body></html>'
finally:
fargv.close()
else:
doctest.testmod()
+285
View File
@@ -0,0 +1,285 @@
#!/usr/bin/env python
# created my Massimo Di Pierro
# license MIT/BSD/GPL
import re
import cgi
import sys
import doctest
from optparse import OptionParser
__all__ = ['render','markmin2latex']
META = 'META'
regex_newlines = re.compile('(\n\r)|(\r\n)')
regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S)
regex_title = re.compile('^#{1} (?P<t>[^\n]+)',re.M)
regex_maps = [
(re.compile('[ \t\r]+\n'),'\n'),
(re.compile('[ \t\r]+\n'),'\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'{\\\\bf \g<t>}'),
(re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"),'{\\it \g<t>}'),
(re.compile('^#{6} (?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{5} (?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{4} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'),
(re.compile('^#{3} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsection{\g<t>}\n'),
(re.compile('^#{2} (?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\section{\g<t>}\n'),
(re.compile('^#{1} (?P<t>[^\n]+)',re.M),''),
(re.compile('^\- +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('^\+ +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'),'\n'),
(re.compile('\n\s+\n'),'\n\n')]
regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n',re.M|re.S)
regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]')
regex_bibitem = re.compile('\-\s*\[\[(?P<t>\S+)\]\]')
regex_image_width = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center) +(?P<w>\d+px)\]\]')
regex_image = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]')
#regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]')
#regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]')
regex_link = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+)\]\]')
regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%]+)',re.M)
regex_commas = re.compile('[ ]+(?P<t>[,;\.])')
regex_noindent = re.compile('\n\n(?P<t>[a-z])')
regex_quote_left = re.compile('"(?=\w)')
regex_quote_right = re.compile('(?=\w\.)"')
def latex_escape(text,pound=True):
text=text.replace('\\','{\\textbackslash}')
for c in '^_&$%{}': text=text.replace(c,'\\'+c)
text=text.replace('\\{\\textbackslash\\}','{\\textbackslash}')
if pound: text=text.replace('#','\\#')
return text
def render(text,extra={},allowed={},sep='p',image_mapper=lambda x:x):
#############################################################
# replace all blocks marked with ``...``:class with META
# store them into segments they will be treated as code
#############################################################
text = str(text or '')
segments, i = [], 0
text = regex_dd.sub('``\g<latex>``:latex ',text)
text = regex_newlines.sub('\n',text)
while True:
item = regex_code.search(text,i)
if not item: break
if item.group()==META:
segments.append((None,None))
text = text[:item.start()]+META+text[item.end():]
else:
c = item.group('c') or ''
if 'code' in allowed and not c in allowed['code']: c = ''
code = item.group('t').replace('!`!','`')
segments.append((code,c))
text = text[:item.start()]+META+text[item.end():]
i=item.start()+3
#############################################################
# do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces
#############################################################
title = regex_title.search(text)
if not title: title='Title'
else: title=title.group('t')
text = latex_escape(text,pound=False)
texts = text.split('## References',1)
text = regex_anchor.sub('\\label{\g<t>}', texts[0])
if len(texts)==2:
text += '\n\\begin{thebibliography}{999}\n'
text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1])
text += '\n\\end{thebibliography}\n'
text = '\n'.join(t.strip() for t in text.split('\n'))
for regex, sub in regex_maps:
text = regex.sub(sub,text)
text=text.replace('#','\\#')
text=text.replace('`',"'")
#############################################################
# process tables and blockquotes
#############################################################
while True:
item = regex_table.search(text)
if not item: break
c = item.group('c') or ''
if 'table' in allowed and not c in allowed['table']: c = ''
content = item.group('t')
if ' | ' in content:
rows = content.replace('\n','\\\\\n').replace(' | ',' & ')
row0,row2 = rows.split('\\\\\n',1)
cols=row0.count(' & ')+1
cal='{'+''.join('l' for j in range(cols))+'}'
tabular = '\\begin{center}\n{\\begin{tabular}'+cal+'\\hline\n' + row0+'\\\\ \\hline\n'+row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}'
if row2.count('\n')>20: tabular='\\newpage\n'+tabular
text = text[:item.start()] + tabular + text[item.end():]
else:
text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():]
#############################################################
# deal with images, videos, audios and links
#############################################################
def sub(x):
f=image_mapper(x.group('k'))
if not f: return None
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % (f)
text = regex_image_width.sub(sub,text)
text = regex_image.sub(sub,text)
text = regex_link.sub('{\\\\footnotesize\\href{\g<k>}{\g<t>}}', text)
text = regex_commas.sub('\g<t>',text)
text = regex_noindent.sub('\n\\\\noindent \g<t>',text)
### fix paths in images
regex=re.compile('\\\\_[\w_]*\.(eps|png|jpg|gif)')
while True:
match=regex.search(text)
if not match: break
text=text[:match.start()]+text[match.start()+1:]
text = regex_quote_left.sub('``',text)
text = regex_quote_right.sub("''",text)
#############################################################
# process all code text
#############################################################
parts = text.split(META)
text = parts[0]
authors = []
for i,(code,b) in enumerate(segments):
if code==None:
html = META
else:
if b=='hidden':
html=''
elif b=='author':
author = latex_escape(code.strip())
authors.append(author)
html=''
elif b=='inxx':
html='\inxx{%s}' % latex_escape(code)
elif b=='cite':
html='~\cite{%s}' % latex_escape(code.strip())
elif b=='ref':
html='~\ref{%s}' % latex_escape(code.strip())
elif b=='latex':
if '\n' in code:
html='\n\\begin{equation}\n%s\n\\end{equation}\n' % code.strip()
else:
html='$%s$' % code.strip()
elif b=='latex_eqnarray':
code=code.strip()
code='\\\\'.join(x.replace('=','&=&',1) for x in code.split('\\\\'))
html='\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code
elif b.startswith('latex_'):
key=b[6:]
html='\\begin{%s}%s\\end{%s}' % (key,code,key)
elif b in extra:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
html = extra[b](code)
elif code[:1]=='\n' or code[:-1]=='\n':
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code.startswith('<') or code.startswith('{{') or code.startswith('http'):
html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code
else:
html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code
else:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
html = '{\\ft %s}' % latex_escape(code)
try:
text = text+html+parts[i+1]
except:
text = text + '... WIKI PROCESSING ERROR ...'
break
text = text.replace(' ~\\cite','~\\cite')
return text, title, authors
WRAPPER = """
\\documentclass[12pt]{article}
\\usepackage{hyperref}
\\usepackage{listings}
\\usepackage{upquote}
\\usepackage{color}
\\usepackage{graphicx}
\\usepackage{grffile}
\\usepackage[utf8x]{inputenc}
\\definecolor{lg}{rgb}{0.9,0.9,0.9}
\\definecolor{dg}{rgb}{0.3,0.3,0.3}
\\def\\ft{\\small\\tt}
\\lstset{
basicstyle=\\footnotesize,
breaklines=true, basicstyle=\\ttfamily\\color{black}\\footnotesize,
keywordstyle=\\bf\\ttfamily,
commentstyle=\\it\\ttfamily,
stringstyle=\\color{dg}\\it\\ttfamily,
numbers=left, numberstyle=\\color{dg}\\tiny, stepnumber=1, numbersep=5pt,
backgroundcolor=\\color{lg}, tabsize=4, showspaces=false,
showstringspaces=false
}
\\title{%(title)s}
\\author{%(author)s}
\\begin{document}
\\maketitle
\\tableofcontents
\\newpage
%(body)s
\\end{document}
"""
def markmin2latex(data, image_mapper=lambda x:x, extra={},
wrapper=WRAPPER):
body, title, authors = render(data, extra=extra, image_mapper=image_mapper)
author = '\n\\and\n'.join(a.replace('\n','\\\\\n\\footnotesize ') for a in authors)
return wrapper % dict(title=title, author=author, body=body)
if __name__ == '__main__':
parser = OptionParser()
parser.add_option("-i", "--info", dest="info",
help="markmin help")
parser.add_option("-t", "--test", dest="test", action="store_true",
default=False)
parser.add_option("-n", "--no_wrapper", dest="no_wrapper",
action="store_true",default=False)
parser.add_option("-1", "--one", dest="one",action="store_true",
default=False,help="switch section for chapter")
parser.add_option("-w", "--wrapper", dest="wrapper", default=False,
help="latex file containing header and footer")
(options, args) = parser.parse_args()
if options.info:
import markmin2html
markmin2latex(markmin2html.__doc__)
elif options.test:
doctest.testmod()
else:
if options.wrapper:
fwrapper = open(options.wrapper,'rb')
try:
wrapper = fwrapper.read()
finally:
fwrapper.close()
elif options.no_wrapper:
wrapper = '%(body)s'
else:
wrapper = WRAPPER
for f in args:
fargs = open(f,'r')
content_data = []
try:
content_data.append(fargs.read())
finally:
fargs.close()
content = '\n'.join(content_data)
output= markmin2latex(content,wrapper=wrapper)
if options.one:
output=output.replace(r'\section*{',r'\chapter*{')
output=output.replace(r'\section{',r'\chapter{')
output=output.replace(r'subsection{',r'section{')
print output
+130
View File
@@ -0,0 +1,130 @@
"""
Created by Massimo Di Pierro
Licese BSD
"""
import subprocess
import os
import os.path
import re
import sys
from tempfile import mkstemp, mkdtemp, NamedTemporaryFile
from markmin2latex import markmin2latex
__all__ = ['markmin2pdf']
def removeall(path):
ERROR_STR= """Error removing %(path)s, %(error)s """
def rmgeneric(path, __func__):
try:
__func__(path)
except OSError, (errno, strerror):
print ERROR_STR % {'path' : path, 'error': strerror }
files=[path]
while files:
file=files[0]
if os.path.isfile(file):
f=os.remove
rmgeneric(file, os.remove)
del files[0]
elif os.path.isdir(file):
nested = os.listdir(file)
if not nested:
rmgeneric(file, os.rmdir)
del files[0]
else:
files = [os.path.join(file,x) for x in nested] + files
def latex2pdf(latex, pdflatex='pdflatex', passes=3):
"""
calls pdflatex in a tempfolder
Arguments:
- pdflatex: path to the pdflatex command. Default is just 'pdflatex'.
- passes: defines how often pdflates should be run in the texfile.
"""
pdflatex=pdflatex
passes=passes
warnings=[]
# setup the envoriment
tmpdir = mkdtemp()
texfile = open(tmpdir+'/test.tex','wb')
texfile.write(latex)
texfile.seek(0)
texfile.close()
texfile = os.path.abspath(texfile.name)
# start doing some work
for i in range(0, passes):
logfd,logname = mkstemp()
outfile=os.fdopen(logfd)
try:
ret = subprocess.call([pdflatex,
'-interaction=nonstopmode',
'-output-format', 'pdf',
'-output-directory', tmpdir,
texfile],
cwd=os.path.dirname(texfile), stdout=outfile,
stderr=subprocess.PIPE)
finally:
outfile.close()
re_errors=re.compile('^\!(.*)$',re.M)
re_warnings=re.compile('^LaTeX Warning\:(.*)$',re.M)
flog = open(logname)
try:
loglines = flog.read()
finally:
flog.close()
errors=re_errors.findall(loglines)
warnings=re_warnings.findall(loglines)
os.unlink(logname)
pdffile=texfile.rsplit('.',1)[0]+'.pdf'
if os.path.isfile(pdffile):
fpdf = open(pdffile, 'rb')
try:
data = fpdf.read()
finally:
fpdf.close()
else:
data = None
removeall(tmpdir)
return data, warnings, errors
def markmin2pdf(text, image_mapper=lambda x: None, extra={}):
return latex2pdf(markmin2latex(text,image_mapper=image_mapper, extra=extra))
if __name__ == '__main__':
import sys
import doctest
import markmin2html
if sys.argv[1:2]==['-h']:
data, warnings, errors = markmin2pdf(markmin2html.__doc__)
if errors:
print 'ERRORS:'+'\n'.join(errors)
print 'WARNGINS:'+'\n'.join(warnings)
else:
print data
elif len(sys.argv)>1:
fargv = open(sys.argv[1],'rb')
try:
data, warnings, errors = markmin2pdf(fargv.read())
finally:
fargv.close()
if errors:
print 'ERRORS:'+'\n'.join(errors)
print 'WARNGINS:'+'\n'.join(warnings)
else:
print data
else:
doctest.testmod()
+255
View File
@@ -0,0 +1,255 @@
Thu, 02 Apr 2009 13:37:49 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.44
* Allowing spaces in the key. (Patch provided by xmm on Launchpad)
* Detecting when the pickler needs a positional argument. (Patch
provided by Brad Clements on Launchpad)
* Moving length check after the compression. (Patch provided by user
Tom on Launchpad)
* Fixing arguments passed to the _Error if invalid read length.
* Fixing the representation of domain sockets. (Patch provided by user
MTB on Launchpad)
* Changing a typo of dead_until. (Patch provided by Shane R. Spencer)
* Providing better error messages (patch provided by Johan Euphrosine).
* Adding get_slabs() function to get stats. (Patch provided
by Nick Verbeck)
Sun, 01 Jun 2008 15:05:11 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.43
* eliott reported a bug in the 1.42 related to the socket timeout code
causing a traceback due to the timeout value not being set.
Sat, 31 May 2008 02:09:17 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.42
* Paul Hummer set up a Launchpad project which I'm going to start using
to track patches and allow users to set up their own bzr branches and
manage marging in the upstream patches with their own.
https://launchpad.net/python-memcached
* Patch from Jehiah Czebotar which does: Changing the calls to
mark_dead() to make them dereference tuples, reducing timeout on
sockets to 3 seconds, settable via setting Host._SOCKET_TIMEOUT.
* Patches from Steve Schwarz for set_multi() to return the full set of
keys if all servers are down. Previously would not report any keys.
* Fix from Steve Schwarz delete_multi() argument "seconds" not being
correctly handled. Changed it to "time" to match all other calls.
* Patch from Peter Wilkinson to support using unix domain sockets.
He reports that tests succeed with with memcached daemons running,
the normal and a domain socket started via
"memcached -s memcached.socket". I massaged it quite a bit.
To use domain sockets, use a connect string of "unix:/path/to/socket"
Note however that if you are using a host name of "unix", it will now
detect "unix:11211" as being a domain socket with the name "11211".
In this case, please use "inet:unix:11211".
Because of this, it is now preferred to use a connect string prefix
of "inet:" or "unix:".
Tue, 29 Apr 2008 21:03:53 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.41
* Patch from Jehiah Czebotar to catch an additional server disconnect
situation.
* Patch from Andrey Petrov to add the "append" and "replace" commands.
Tue, 18 Sep 2007 20:52:09 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.40
* Updated setup.py file that uses distutils provided by Kai Lautaportti.
* Prevent keys from containing ASCII character 127 as well, patch provided
by Philip Neustrom.
* Added ability to overload the persistent_load/id, patch provided by
Steve Schwarz.
* Fixed ability to pass (server_hash,key) in place of key in Client.set()
Reported by Alexander Klyuev.
Tue, 14 Aug 2007 14:43:27 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.39
* Michael Krause reports the previous version doesn't work for
_val_to_store_info() calls because it's defined as a staticmethod.
Removing staticmethod decorator. Also confirmed by Kai Lautaportti,
with suggested fix of removing staticmethod.
Fri, 10 Aug 2007 17:50:13 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.38
* Matt McClanahan submitted a patch that allow add() to have a
min_compress_len argument.
* Steve Schwarz submitted a patch allowing user-defined picklers.
* Michael Krause suggested checking the return value to prevent an
exception from being raised in _set() when a value is too large to be
stored.
Fri, 27 Jul 2007 01:55:48 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.37
* Fixing call from add() to _set() with parameter for min_compress_len.
Reported by Jeff Fisher.
Thu, 07 Jun 2007 04:10:31 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.36
* Patch by Dave St.Germain to make the Client() class sub-class
threadlocal to help with multi-threading issues. Only available in
Python 2.4 and above.
* Patch by James Robinson with:
1) new set_multi method.
2) factored out determining the flags, length, and value to store
from set() into method _val_to_store_info() for use by both set()
and set_multi().
3) send_cmds() method on host which doesn't apply the trailing '\r\n'
for use by set_multi.
4) check_key() extended a bit to allow for testing the prefix passed
to set_multi just once, not once per each key.
5) Patch also enables support for auto compression in set, set_multi,
and replace.
* Suggestion by Helge Tesdal, fixes in check_key for non-string keys.
* NOTE: On a farm of clients with multiple servers, all clients will
need to be upgraded to this version. The next patch changes the
server hash.
* Philip Neustrom supplied a patch to change the server hash function to
binascii.crc32. The original "hash()" call is not cross-platform, so
big and little endian systems accessing the same memcache may end up
hitting different servers. Restore the old functionality by calling:
"memcached.serverHashFunction = hash" after importing memcache.
* Philip Neustrom points out that passing Unicode keys or values causes
problems because len(key) or len(value) is not equal to the number of
bytes that are required to store the key/value. Philip provides a
patch which raises an exception in this case. Raises
memcache.Client.MemcachedStringEncodingError exception in this case.
* NOTE: If you recompiled memcached to increase the default 1MB max
value size, you will need to call "memcached.MAX_SERVER_VALUE_LENGTH = N"
or memcached will not store values larger than the default 1MB.
* Philip Neustrom includes another patch which checks that the key
doesn't exceed the memcache server's max size. If it does, the item
is silently not stored.
* Philip Neustrom added a bunch of sanity checks.
* Jehiah Czebotar provided a patch to make the add() and replace()
functions return 0 when the add or replace fails, similar to how set()
works.
Sat, 16 Sep 2006 18:31:46 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.34
* In get_multi, if the recv loop reads 0 bytes, raising an EOFError.
Identified by Jim Baker.
Tue, 05 Sep 2006 14:06:50 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.33
* Including patch from Yoshinori K. Okuji to read in larger chunks for
readline() calls. This should dramatically improve performance under
some circumstances.
Sun, 03 Sep 2006 14:02:03 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.32
* Including patch from Philip Neustrom which checks keys sent to the
server for length and bad characters.
Sat, 20 May 2006 14:51:28 -0600 Sean Reifschneider <jafo@tummy.com>
* Version 1.31
* Rolled version 1.30 since the Danga folks are now listing this
version as the official version. Removing the "tummy" from the version
number, and incrementing so that it's clear it's more recent than "1.2".
* Patch applied from Simon Forman for handling of weighted hosts.
* Added a little more meat to the README.
Sat, 28 Jan 2006 15:59:50 -0700 Sean Reifschneider <jafo@tummy.com>
* cludwin at socallocal suggested that the write-combining with
sendall() may not be beneficial. After testing on both SMP and non-SMP
machines, I can't see a significant benefit to not doing the
write-combining, even on large strings. The benefits of write-combining
on smaller strings seems to be significant on UP machines in tight loops.
Even on strings that are larger than 2MB, there seems to be no benefit to
splitting out the writes.
Sun, 18 Sep 2005 18:56:31 -0600 Sean Reifschneider <jafo@tummy.com>
* Changing a printf to debuglog and catching a pickle exception, patch
submitted by Justin Azoff.
Thu, 14 Jul 2005 11:17:30 -0700 Sean Reifschneider <jafo@tummy.com>
* Alex Stapleton found that the sendall call was slow for writing data
larger than several kilobytes. I had him test a change to his patch,
which worked as well, but was simpler. The code now does two sendall
calls, one for the data and one for the line termination, if the data is
larger than 100 bytes.
Thu, 7 Apr 2005 14:45:44 -0700 Sean Reifschneider <jafo@tummy.com>
* Incorporating some fixes to get_multi() from Bo Yang
Mon, 13 Dec 2004 02:35:17 -0700 Sean Reifschneider <jafo@tummy.com>
* Simplifying the readline() function and speeding it up ~25%.
* Fixing a bug in readline() if the server drops, mark_dead() was not
being properly called.
Sun, 12 Dec 2004 18:56:33 -0700 Sean Reifschneider <jafo@tummy.com>
* Adding "stats()" and "flush_all()" methods.
Thu, 10 Aug 2003 12:17:50 -0700 Evan Martin <martine@danga.com>
* Slightly more verbose self-test output.
* Fix mark_dead() to use proper classname.
* Make pooltest.py run from the test directory.
Thu, 07 Aug 2003 16:32:32 -0700 Evan Martin <martine@danga.com>
* Add incr, decr, and delete.
* Better Python (based on comments from Uriah Welcome).
* Docs, using epydoc.
Thu, 07 Aug 2003 14:20:27 -0700 Evan Martin <martine@danga.com>
* Initial prerelease.
+10
View File
@@ -0,0 +1,10 @@
Metadata-Version: 1.0
Name: python-memcached
Version: 1.44
Summary: A Python memcached client library.
Home-page: http://www.danga.com/memcached/
Author: Evan Martin
Author-email: martine@danga.com
License: Python
Description: A Python memcached client library.
Platform: UNKNOWN
+7
View File
@@ -0,0 +1,7 @@
This package was originally written by Evan Martin of Danga.
Sean Reifschneider of tummy.com, ltd. has taken over maintenance of it.
This software is a 100% Python interface to the memcached memory cache
daemon. It is the client side software which allows storing values in one
or more, possibly remote, memcached servers. Search google for memcached
for more information.
+89
View File
@@ -0,0 +1,89 @@
from gluon.contrib.memcache.memcache import Client
from gluon.cache import CacheAbstract
import time
"""
examle of usage:
cache.memcache = MemcacheClient(request,[127.0.0.1:11211],debug=true)
"""
import cPickle as pickle
import thread
locker = thread.allocate_lock()
def MemcacheClient(*a, **b):
locker.acquire()
try:
if not hasattr(MemcacheClient, '__mc_instance'):
MemcacheClient.__mc_instance = _MemcacheClient(*a, **b)
finally:
locker.release()
return MemcacheClient.__mc_instance
class _MemcacheClient(Client):
meta_storage = {}
def __init__(self, request, servers, debug=0, pickleProtocol=0,
pickler=pickle.Pickler, unpickler=pickle.Unpickler,
pload=None, pid=None):
self.request=request
if request:
app = request.application
else:
app = ''
Client.__init__(self, servers, debug, pickleProtocol,
pickler, unpickler, pload, pid)
if not app in self.meta_storage:
self.storage = self.meta_storage[app] = {
CacheAbstract.cache_stats_name: {
'hit_total': 0,
'misses': 0,
}}
else:
self.storage = self.meta_storage[app]
def __call__(self, key, f, time_expire=300):
if time_expire == None:
time_expire = 10**10
# this must be commented because get and set are redefined
# key = self.__keyFormat__(key)
value = None
obj = self.get(key)
if obj:
value = obj
elif f is None:
if obj: self.delete(key)
else:
value = f()
self.set(key, value, time_expire)
return value
def increment(self, key, value=1, time_expire=300):
newKey = self.__keyFormat__(key)
obj = Client.get(self, newKey)
if obj:
return Client.incr(self, newKey, value)
else:
Client.set(self, newKey, value, time_expire)
return value
def set(self, key, value, time_expire=300):
newKey = self.__keyFormat__(key)
return Client.set(self, newKey, value, time_expire)
def get(self, key):
newKey = self.__keyFormat__(key)
return Client.get(self, newKey)
def delete(self, key):
newKey = self.__keyFormat__(key)
return Client.delete(self, newKey)
def __keyFormat__(self, key):
return '%s/%s' % (self.request.application, key.replace(' ', '_'))
File diff suppressed because it is too large Load Diff
+908
View File
@@ -0,0 +1,908 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of web2py Web Framework (Copyrighted, 2007-2009).
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu> and
Robin B <robi123@gmail.com>.
License: GPL v2
"""
__all__ = ['MEMDB', 'Field']
import re
import sys
import os
import types
import datetime
import thread
import cStringIO
import csv
import copy
import gluon.validators as validators
from gluon.storage import Storage
import random
SQL_DIALECTS = {'memcache': {
'boolean': bool,
'string': unicode,
'text': unicode,
'password': unicode,
'blob': unicode,
'upload': unicode,
'integer': long,
'double': float,
'date': datetime.date,
'time': datetime.time,
'datetime': datetime.datetime,
'id': int,
'reference': int,
'lower': None,
'upper': None,
'is null': 'IS NULL',
'is not null': 'IS NOT NULL',
'extract': None,
'left join': None,
}}
def cleanup(text):
if re.compile('[^0-9a-zA-Z_]').findall(text):
raise SyntaxError, \
'Can\'t cleanup \'%s\': only [0-9a-zA-Z_] allowed in table and field names' % text
return text
def assert_filter_fields(*fields):
for field in fields:
if isinstance(field, (Field, Expression)) and field.type\
in ['text', 'blob']:
raise SyntaxError, 'AppEngine does not index by: %s'\
% field.type
def dateobj_to_datetime(object):
# convert dates,times to datetimes for AppEngine
if isinstance(object, datetime.date):
object = datetime.datetime(object.year, object.month,
object.day)
if isinstance(object, datetime.time):
object = datetime.datetime(
1970,
1,
1,
object.hour,
object.minute,
object.second,
object.microsecond,
)
return object
def sqlhtml_validators(field_type, length):
v = {
'boolean': [],
'string': validators.IS_LENGTH(length),
'text': [],
'password': validators.IS_LENGTH(length),
'blob': [],
'upload': [],
'double': validators.IS_FLOAT_IN_RANGE(-1e100, 1e100),
'integer': validators.IS_INT_IN_RANGE(-1e100, 1e100),
'date': validators.IS_DATE(),
'time': validators.IS_TIME(),
'datetime': validators.IS_DATETIME(),
'reference': validators.IS_INT_IN_RANGE(0, 1e100),
}
try:
return v[field_type[:9]]
except KeyError:
return []
class DALStorage(dict):
"""
a dictionary that let you do d['a'] as well as d.a
"""
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, value):
if key in self:
raise SyntaxError, 'Object \'%s\'exists and cannot be redefined' % key
self[key] = value
def __repr__(self):
return '<DALStorage ' + dict.__repr__(self) + '>'
class SQLCallableList(list):
def __call__(self):
return copy.copy(self)
class MEMDB(DALStorage):
"""
an instance of this class represents a database connection
Example::
db=MEMDB(Client())
db.define_table('tablename',Field('fieldname1'),
Field('fieldname2'))
"""
def __init__(self, client):
self._dbname = 'memdb'
self['_lastsql'] = ''
self.tables = SQLCallableList()
self._translator = SQL_DIALECTS['memcache']
self.client = client
def define_table(
self,
tablename,
*fields,
**args
):
tablename = cleanup(tablename)
if tablename in dir(self) or tablename[0] == '_':
raise SyntaxError, 'invalid table name: %s' % tablename
if not tablename in self.tables:
self.tables.append(tablename)
else:
raise SyntaxError, 'table already defined: %s' % tablename
t = self[tablename] = Table(self, tablename, *fields)
t._create()
return t
def __call__(self, where=''):
return Set(self, where)
class SQLALL(object):
def __init__(self, table):
self.table = table
class Table(DALStorage):
"""
an instance of this class represents a database table
Example::
db=MEMDB(Client())
db.define_table('users',Field('name'))
db.users.insert(name='me')
"""
def __init__(
self,
db,
tablename,
*fields
):
self._db = db
self._tablename = tablename
self.fields = SQLCallableList()
self._referenced_by = []
fields = list(fields)
fields.insert(0, Field('id', 'id'))
for field in fields:
self.fields.append(field.name)
self[field.name] = field
field._tablename = self._tablename
field._table = self
field._db = self._db
self.ALL = SQLALL(self)
def _create(self):
fields = []
myfields = {}
for k in self.fields:
field = self[k]
attr = {}
if not field.type[:9] in ['id', 'reference']:
if field.notnull:
attr = dict(required=True)
if field.type[:2] == 'id':
continue
if field.type[:9] == 'reference':
referenced = field.type[10:].strip()
if not referenced:
raise SyntaxError, \
'Table %s: reference \'%s\' to nothing!' % (self._tablename, k)
if not referenced in self._db:
raise SyntaxError, \
'Table: table %s does not exist' % referenced
referee = self._db[referenced]
ftype = \
self._db._translator[field.type[:9]](
self._db[referenced]._tableobj)
if self._tablename in referee.fields: # ## THIS IS OK
raise SyntaxError, \
'Field: table \'%s\' has same name as a field ' \
'in referenced table \'%s\'' % (self._tablename, referenced)
self._db[referenced]._referenced_by.append((self._tablename,
field.name))
elif not field.type in self._db._translator\
or not self._db._translator[field.type]:
raise SyntaxError, 'Field: unkown field type %s' % field.type
self._tableobj = self._db.client
return None
def create(self):
# nothing to do, here for backward compatility
pass
def drop(self):
# nothing to do, here for backward compatibility
self._db(self.id > 0).delete()
def insert(self, **fields):
id = self._create_id()
if self.update(id, **fields):
return long(id)
else:
return None
def get(self, id):
val = self._tableobj.get(self._id_to_key(id))
if val:
return Storage(val)
else:
return None
def update(self, id, **fields):
for field in fields:
if not field in fields and self[field].default\
!= None:
fields[field] = self[field].default
if field in fields:
fields[field] = obj_represent(fields[field],
self[field].type, self._db)
return self._tableobj.set(self._id_to_key(id), fields)
def delete(self, id):
return self._tableobj.delete(self._id_to_key(id))
def _shard_key(self, shard):
return self._id_to_key('s/%s' % shard)
def _id_to_key(self, id):
return '__memdb__/t/%s/k/%s' % (self._tablename, str(id))
def _create_id(self):
shard = random.randint(10, 99)
shard_id = self._shard_key(shard)
id = self._tableobj.incr(shard_id)
if not id:
if self._tableobj.set(shard_id, '0'):
id = 0
else:
raise Exception, 'cannot set memcache'
return long(str(shard) + str(id))
def __str__(self):
return self._tablename
class Expression(object):
def __init__(
self,
name,
type='string',
db=None,
):
(self.name, self.type, self._db) = (name, type, db)
def __str__(self):
return self.name
def __or__(self, other): # for use in sortby
assert_filter_fields(self, other)
return Expression(self.name + '|' + other.name, None, None)
def __invert__(self):
assert_filter_fields(self)
return Expression('-' + self.name, self.type, None)
# for use in Query
def __eq__(self, value):
return Query(self, '=', value)
def __ne__(self, value):
return Query(self, '!=', value)
def __lt__(self, value):
return Query(self, '<', value)
def __le__(self, value):
return Query(self, '<=', value)
def __gt__(self, value):
return Query(self, '>', value)
def __ge__(self, value):
return Query(self, '>=', value)
# def like(self,value): return Query(self,' LIKE ',value)
# def belongs(self,value): return Query(self,' IN ',value)
# for use in both Query and sortby
def __add__(self, other):
return Expression('%s+%s' % (self, other), 'float', None)
def __sub__(self, other):
return Expression('%s-%s' % (self, other), 'float', None)
def __mul__(self, other):
return Expression('%s*%s' % (self, other), 'float', None)
def __div__(self, other):
return Expression('%s/%s' % (self, other), 'float', None)
class Field(Expression):
"""
an instance of this class represents a database field
example::
a = Field(name, 'string', length=32, required=False,
default=None, requires=IS_NOT_EMPTY(), notnull=False,
unique=False, uploadfield=True)
to be used as argument of GQLDB.define_table
allowed field types:
string, boolean, integer, double, text, blob,
date, time, datetime, upload, password
strings must have a length or 512 by default.
fields should have a default or they will be required in SQLFORMs
the requires argument are used to validate the field input in SQLFORMs
"""
def __init__(
self,
fieldname,
type='string',
length=None,
default=None,
required=False,
requires=sqlhtml_validators,
ondelete='CASCADE',
notnull=False,
unique=False,
uploadfield=True,
):
self.name = cleanup(fieldname)
if fieldname in dir(Table) or fieldname[0] == '_':
raise SyntaxError, 'Field: invalid field name: %s' % fieldname
if isinstance(type, Table):
type = 'reference ' + type._tablename
if not length:
length = 512
self.type = type # 'string', 'integer'
self.length = length # the length of the string
self.default = default # default value for field
self.required = required # is this field required
self.ondelete = ondelete.upper() # this is for reference fields only
self.notnull = notnull
self.unique = unique
self.uploadfield = uploadfield
if requires == sqlhtml_validators:
requires = sqlhtml_validators(type, length)
elif requires is None:
requires = []
self.requires = requires # list of validators
def formatter(self, value):
if value is None or not self.requires:
return value
if not isinstance(self.requires, (list, tuple)):
requires = [self.requires]
else:
requires = copy.copy(self.requires)
requires.reverse()
for item in requires:
if hasattr(item, 'formatter'):
value = item.formatter(value)
return value
def __str__(self):
return '%s.%s' % (self._tablename, self.name)
MEMDB.Field = Field # ## required by gluon/globals.py session.connect
def obj_represent(object, fieldtype, db):
if object != None:
if fieldtype == 'date' and not isinstance(object,
datetime.date):
(y, m, d) = [int(x) for x in str(object).strip().split('-')]
object = datetime.date(y, m, d)
elif fieldtype == 'time' and not isinstance(object, datetime.time):
time_items = [int(x) for x in str(object).strip().split(':')[:3]]
if len(time_items) == 3:
(h, mi, s) = time_items
else:
(h, mi, s) = time_items + [0]
object = datetime.time(h, mi, s)
elif fieldtype == 'datetime' and not isinstance(object,
datetime.datetime):
(y, m, d) = [int(x) for x in
str(object)[:10].strip().split('-')]
time_items = [int(x) for x in
str(object)[11:].strip().split(':')[:3]]
if len(time_items) == 3:
(h, mi, s) = time_items
else:
(h, mi, s) = time_items + [0]
object = datetime.datetime(
y,
m,
d,
h,
mi,
s,
)
elif fieldtype == 'integer' and not isinstance(object, long):
object = long(object)
return object
class QueryException:
def __init__(self, **a):
self.__dict__ = a
class Query(object):
"""
A query object necessary to define a set.
It can be stored or can be passed to GQLDB.__call__() to obtain a Set
Example:
query=db.users.name=='Max'
set=db(query)
records=set.select()
"""
def __init__(
self,
left,
op=None,
right=None,
):
if isinstance(right, (Field, Expression)):
raise SyntaxError, \
'Query: right side of filter must be a value or entity'
if isinstance(left, Field) and left.name == 'id':
if op == '=':
self.get_one = \
QueryException(tablename=left._tablename,
id=long(right))
return
else:
raise SyntaxError, 'only equality by id is supported'
raise SyntaxError, 'not supported'
def __str__(self):
return str(self.left)
class Set(object):
"""
As Set represents a set of records in the database,
the records are identified by the where=Query(...) object.
normally the Set is generated by GQLDB.__call__(Query(...))
given a set, for example
set=db(db.users.name=='Max')
you can:
set.update(db.users.name='Massimo')
set.delete() # all elements in the set
set.select(orderby=db.users.id,groupby=db.users.name,limitby=(0,10))
and take subsets:
subset=set(db.users.id<5)
"""
def __init__(self, db, where=None):
self._db = db
self._tables = []
self.filters = []
if hasattr(where, 'get_all'):
self.where = where
self._tables.insert(0, where.get_all)
elif hasattr(where, 'get_one') and isinstance(where.get_one,
QueryException):
self.where = where.get_one
else:
# find out which tables are involved
if isinstance(where, Query):
self.filters = where.left
self.where = where
self._tables = [field._tablename for (field, op, val) in
self.filters]
def __call__(self, where):
if isinstance(self.where, QueryException) or isinstance(where,
QueryException):
raise SyntaxError, \
'neither self.where nor where can be a QueryException instance'
if self.where:
return Set(self._db, self.where & where)
else:
return Set(self._db, where)
def _get_table_or_raise(self):
tablenames = list(set(self._tables)) # unique
if len(tablenames) < 1:
raise SyntaxError, 'Set: no tables selected'
if len(tablenames) > 1:
raise SyntaxError, 'Set: no join in appengine'
return self._db[tablenames[0]]._tableobj
def _getitem_exception(self):
(tablename, id) = (self.where.tablename, self.where.id)
fields = self._db[tablename].fields
self.colnames = ['%s.%s' % (tablename, t) for t in fields]
item = self._db[tablename].get(id)
return (item, fields, tablename, id)
def _select_except(self):
(item, fields, tablename, id) = self._getitem_exception()
if not item:
return []
new_item = []
for t in fields:
if t == 'id':
new_item.append(long(id))
else:
new_item.append(getattr(item, t))
r = [new_item]
return Rows(self._db, r, *self.colnames)
def select(self, *fields, **attributes):
"""
Always returns a Rows object, even if it may be empty
"""
if isinstance(self.where, QueryException):
return self._select_except()
else:
raise SyntaxError, 'select arguments not supported'
def count(self):
return len(self.select())
def delete(self):
if isinstance(self.where, QueryException):
(item, fields, tablename, id) = self._getitem_exception()
if not item:
return
self._db[tablename].delete(id)
else:
raise Exception, 'deletion not implemented'
def update(self, **update_fields):
if isinstance(self.where, QueryException):
(item, fields, tablename, id) = self._getitem_exception()
if not item:
return
for (key, value) in update_fields.items():
setattr(item, key, value)
self._db[tablename].update(id, **item)
else:
raise Exception, 'update not implemented'
def update_record(
t,
s,
id,
a,
):
item = s.get(id)
for (key, value) in a.items():
t[key] = value
setattr(item, key, value)
s.update(id, **item)
class Rows(object):
"""
A wrapper for the return value of a select. It basically represents a table.
It has an iterator and each row is represented as a dictionary.
"""
# ## this class still needs some work to care for ID/OID
def __init__(
self,
db,
response,
*colnames
):
self._db = db
self.colnames = colnames
self.response = response
def __len__(self):
return len(self.response)
def __getitem__(self, i):
if i >= len(self.response) or i < 0:
raise SyntaxError, 'Rows: no such row: %i' % i
if len(self.response[0]) != len(self.colnames):
raise SyntaxError, 'Rows: internal error'
row = DALStorage()
for j in xrange(len(self.colnames)):
value = self.response[i][j]
if isinstance(value, unicode):
value = value.encode('utf-8')
packed = self.colnames[j].split('.')
try:
(tablename, fieldname) = packed
except:
if not '_extra' in row:
row['_extra'] = DALStorage()
row['_extra'][self.colnames[j]] = value
continue
table = self._db[tablename]
field = table[fieldname]
if not tablename in row:
row[tablename] = DALStorage()
if field.type[:9] == 'reference':
referee = field.type[10:].strip()
rid = value
row[tablename][fieldname] = rid
elif field.type == 'boolean' and value != None:
# row[tablename][fieldname]=Set(self._db[referee].id==rid)
if value == True or value == 'T':
row[tablename][fieldname] = True
else:
row[tablename][fieldname] = False
elif field.type == 'date' and value != None\
and not isinstance(value, datetime.date):
(y, m, d) = [int(x) for x in
str(value).strip().split('-')]
row[tablename][fieldname] = datetime.date(y, m, d)
elif field.type == 'time' and value != None\
and not isinstance(value, datetime.time):
time_items = [int(x) for x in
str(value).strip().split(':')[:3]]
if len(time_items) == 3:
(h, mi, s) = time_items
else:
(h, mi, s) = time_items + [0]
row[tablename][fieldname] = datetime.time(h, mi, s)
elif field.type == 'datetime' and value != None\
and not isinstance(value, datetime.datetime):
(y, m, d) = [int(x) for x in
str(value)[:10].strip().split('-')]
time_items = [int(x) for x in
str(value)[11:].strip().split(':')[:3]]
if len(time_items) == 3:
(h, mi, s) = time_items
else:
(h, mi, s) = time_items + [0]
row[tablename][fieldname] = datetime.datetime(
y,
m,
d,
h,
mi,
s,
)
else:
row[tablename][fieldname] = value
if fieldname == 'id':
id = row[tablename].id
row[tablename].update_record = lambda t = row[tablename], \
s = self._db[tablename], id = id, **a: update_record(t,
s, id, a)
for (referee_table, referee_name) in \
table._referenced_by:
s = self._db[referee_table][referee_name]
row[tablename][referee_table] = Set(self._db, s
== id)
if len(row.keys()) == 1:
return row[row.keys()[0]]
return row
def __iter__(self):
"""
iterator over records
"""
for i in xrange(len(self)):
yield self[i]
def __str__(self):
"""
serializes the table into a csv file
"""
s = cStringIO.StringIO()
writer = csv.writer(s)
writer.writerow(self.colnames)
c = len(self.colnames)
for i in xrange(len(self)):
row = [self.response[i][j] for j in xrange(c)]
for k in xrange(c):
if isinstance(row[k], unicode):
row[k] = row[k].encode('utf-8')
writer.writerow(row)
return s.getvalue()
def xml(self):
"""
serializes the table using sqlhtml.SQLTABLE (if present)
"""
return sqlhtml.SQLTABLE(self).xml()
def test_all():
"""
How to run from web2py dir:
export PYTHONPATH=.:YOUR_PLATFORMS_APPENGINE_PATH
python gluon/contrib/memdb.py
Setup the UTC timezone and database stubs
>>> import os
>>> os.environ['TZ'] = 'UTC'
>>> import time
>>> if hasattr(time, 'tzset'):
... time.tzset()
>>>
>>> from google.appengine.api import apiproxy_stub_map
>>> from google.appengine.api.memcache import memcache_stub
>>> apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()
>>> apiproxy_stub_map.apiproxy.RegisterStub('memcache', memcache_stub.MemcacheServiceStub())
Create a table with all possible field types
>>> from google.appengine.api.memcache import Client
>>> db=MEMDB(Client())
>>> tmp=db.define_table('users', Field('stringf','string',length=32,required=True), Field('booleanf','boolean',default=False), Field('passwordf','password',notnull=True), Field('blobf','blob'), Field('uploadf','upload'), Field('integerf','integer',unique=True), Field('doublef','double',unique=True,notnull=True), Field('datef','date',default=datetime.date.today()), Field('timef','time'), Field('datetimef','datetime'), migrate='test_user.table')
Insert a field
>>> user_id = db.users.insert(stringf='a',booleanf=True,passwordf='p',blobf='0A', uploadf=None, integerf=5,doublef=3.14, datef=datetime.date(2001,1,1), timef=datetime.time(12,30,15), datetimef=datetime.datetime(2002,2,2,12,30,15))
>>> user_id != None
True
Select all
# >>> all = db().select(db.users.ALL)
Drop the table
# >>> db.users.drop()
Select many entities
>>> tmp = db.define_table(\"posts\", Field('body','text'), Field('total','integer'), Field('created_at','datetime'))
>>> many = 20 #2010 # more than 1000 single fetch limit (it can be slow)
>>> few = 5
>>> most = many - few
>>> 0 < few < most < many
True
>>> for i in range(many):
... f=db.posts.insert(body='', total=i,created_at=datetime.datetime(2008, 7, 6, 14, 15, 42, i))
>>>
# test timezones
>>> class TZOffset(datetime.tzinfo):
... def __init__(self,offset=0):
... self.offset = offset
... def utcoffset(self, dt): return datetime.timedelta(hours=self.offset)
... def dst(self, dt): return datetime.timedelta(0)
... def tzname(self, dt): return 'UTC' + str(self.offset)
...
>>> SERVER_OFFSET = -8
>>>
>>> stamp = datetime.datetime(2008, 7, 6, 14, 15, 42, 828201)
>>> post_id = db.posts.insert(created_at=stamp,body='body1')
>>> naive_stamp = db(db.posts.id==post_id).select()[0].created_at
>>> utc_stamp=naive_stamp.replace(tzinfo=TZOffset())
>>> server_stamp = utc_stamp.astimezone(TZOffset(SERVER_OFFSET))
>>> stamp == naive_stamp
True
>>> utc_stamp == server_stamp
True
>>> rows = db(db.posts.id==post_id).select()
>>> len(rows) == 1
True
>>> rows[0].body == 'body1'
True
>>> db(db.posts.id==post_id).delete()
>>> rows = db(db.posts.id==post_id).select()
>>> len(rows) == 0
True
>>> id = db.posts.insert(total='0') # coerce str to integer
>>> rows = db(db.posts.id==id).select()
>>> len(rows) == 1
True
>>> rows[0].total == 0
True
Examples of insert, select, update, delete
>>> tmp=db.define_table('person', Field('name'), Field('birth','date'), migrate='test_person.table')
>>> marco_id=db.person.insert(name=\"Marco\",birth='2005-06-22')
>>> person_id=db.person.insert(name=\"Massimo\",birth='1971-12-21')
>>> me=db(db.person.id==person_id).select()[0] # test select
>>> me.name
'Massimo'
>>> db(db.person.id==person_id).update(name='massimo') # test update
>>> me = db(db.person.id==person_id).select()[0]
>>> me.name
'massimo'
>>> str(me.birth)
'1971-12-21'
# resave date to ensure it comes back the same
>>> me=db(db.person.id==person_id).update(birth=me.birth) # test update
>>> me = db(db.person.id==person_id).select()[0]
>>> me.birth
datetime.date(1971, 12, 21)
>>> db(db.person.id==marco_id).delete() # test delete
>>> len(db(db.person.id==marco_id).select())
0
Update a single record
>>> me.update_record(name=\"Max\")
>>> me.name
'Max'
>>> me = db(db.person.id == person_id).select()[0]
>>> me.name
'Max'
"""
SQLField = Field
SQLTable = Table
SQLXorable = Expression
SQLQuery = Query
SQLSet = Set
SQLRows = Rows
SQLStorage = DALStorage
if __name__ == '__main__':
import doctest
doctest.testmod()
View File
+230
View File
@@ -0,0 +1,230 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""`cssmin` - A Python port of the YUI CSS compressor."""
"""
Home page: https://github.com/zacharyvoase/cssmin
License: BSD: https://github.com/zacharyvoase/cssmin/blob/master/LICENSE
Original author: Zachary Voase
Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
"""
from StringIO import StringIO # The pure-Python StringIO supports unicode.
import re
__version__ = '0.1.4'
def remove_comments(css):
"""Remove all CSS comment blocks."""
iemac = False
preserve = False
comment_start = css.find("/*")
while comment_start >= 0:
# Preserve comments that look like `/*!...*/`.
# Slicing is used to make sure we don"t get an IndexError.
preserve = css[comment_start + 2:comment_start + 3] == "!"
comment_end = css.find("*/", comment_start + 2)
if comment_end < 0:
if not preserve:
css = css[:comment_start]
break
elif comment_end >= (comment_start + 2):
if css[comment_end - 1] == "\\":
# This is an IE Mac-specific comment; leave this one and the
# following one alone.
comment_start = comment_end + 2
iemac = True
elif iemac:
comment_start = comment_end + 2
iemac = False
elif not preserve:
css = css[:comment_start] + css[comment_end + 2:]
else:
comment_start = comment_end + 2
comment_start = css.find("/*", comment_start)
return css
def remove_unnecessary_whitespace(css):
"""Remove unnecessary whitespace characters."""
def pseudoclasscolon(css):
"""
Prevents 'p :link' from becoming 'p:link'.
Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is
translated back again later.
"""
regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)")
match = regex.search(css)
while match:
css = ''.join([
css[:match.start()],
match.group().replace(":", "___PSEUDOCLASSCOLON___"),
css[match.end():]])
match = regex.search(css)
return css
css = pseudoclasscolon(css)
# Remove spaces from before things.
css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css)
# If there is a `@charset`, then only allow one, and move to the beginning.
css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css)
css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css)
# Put the space back in for a few cases, such as `@media screen` and
# `(-webkit-min-device-pixel-ratio:0)`.
css = re.sub(r"\band\(", "and (", css)
# Put the colons back.
css = css.replace('___PSEUDOCLASSCOLON___', ':')
# Remove spaces from after things.
css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css)
return css
def remove_unnecessary_semicolons(css):
"""Remove unnecessary semicolons."""
return re.sub(r";+\}", "}", css)
def remove_empty_rules(css):
"""Remove empty rules."""
return re.sub(r"[^\}\{]+\{\}", "", css)
def normalize_rgb_colors_to_hex(css):
"""Convert `rgb(51,102,153)` to `#336699`."""
regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)")
match = regex.search(css)
while match:
colors = map(lambda s: s.strip(), match.group(1).split(","))
hexcolor = '#%.2x%.2x%.2x' % tuple(map(int, colors))
css = css.replace(match.group(), hexcolor)
match = regex.search(css)
return css
def condense_zero_units(css):
"""Replace `0(px, em, %, etc)` with `0`."""
return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css)
def condense_multidimensional_zeros(css):
"""Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`."""
css = css.replace(":0 0 0 0;", ":0;")
css = css.replace(":0 0 0;", ":0;")
css = css.replace(":0 0;", ":0;")
# Revert `background-position:0;` to the valid `background-position:0 0;`.
css = css.replace("background-position:0;", "background-position:0 0;")
return css
def condense_floating_points(css):
"""Replace `0.6` with `.6` where possible."""
return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css)
def condense_hex_colors(css):
"""Shorten colors from #AABBCC to #ABC where possible."""
regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])")
match = regex.search(css)
while match:
first = match.group(3) + match.group(5) + match.group(7)
second = match.group(4) + match.group(6) + match.group(8)
if first.lower() == second.lower():
css = css.replace(match.group(), match.group(1) + match.group(2) + '#' + first)
match = regex.search(css, match.end() - 3)
else:
match = regex.search(css, match.end())
return css
def condense_whitespace(css):
"""Condense multiple adjacent whitespace characters into one."""
return re.sub(r"\s+", " ", css)
def condense_semicolons(css):
"""Condense multiple adjacent semicolon characters into one."""
return re.sub(r";;+", ";", css)
def wrap_css_lines(css, line_length):
"""Wrap the lines of the given CSS to an approximate length."""
lines = []
line_start = 0
for i, char in enumerate(css):
# It's safe to break after `}` characters.
if char == '}' and (i - line_start >= line_length):
lines.append(css[line_start:i + 1])
line_start = i + 1
if line_start < len(css):
lines.append(css[line_start:])
return '\n'.join(lines)
def cssmin(css, wrap=None):
css = remove_comments(css)
css = condense_whitespace(css)
# A pseudo class for the Box Model Hack
# (see http://tantek.com/CSS/Examples/boxmodelhack.html)
css = css.replace('"\\"}\\""', "___PSEUDOCLASSBMH___")
css = remove_unnecessary_whitespace(css)
css = remove_unnecessary_semicolons(css)
css = condense_zero_units(css)
css = condense_multidimensional_zeros(css)
css = condense_floating_points(css)
css = normalize_rgb_colors_to_hex(css)
css = condense_hex_colors(css)
if wrap is not None:
css = wrap_css_lines(css, wrap)
css = css.replace("___PSEUDOCLASSBMH___", '"\\"}\\""')
css = condense_semicolons(css)
return css.strip()
def main():
import optparse
import sys
p = optparse.OptionParser(
prog="cssmin", version=__version__,
usage="%prog [--wrap N]",
description="""Reads raw CSS from stdin, and writes compressed CSS to stdout.""")
p.add_option(
'-w', '--wrap', type='int', default=None, metavar='N',
help="Wrap output to approximately N chars per line.")
options, args = p.parse_args()
sys.stdout.write(cssmin(sys.stdin.read(), wrap=options.wrap))
if __name__ == '__main__':
main()
+374
View File
@@ -0,0 +1,374 @@
#!/usr/bin/env python
# -*- coding: ascii -*-
#
# Copyright 2011
# Andr\xe9 Malo or his licensors, as applicable
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
=====================
Javascript Minifier
=====================
Javascript Minifier based on `jsmin.c by Douglas Crockford`_\.
This module is a re-implementation based on the semantics of jsmin.c. Usually
it produces the same results. It differs in the following ways:
- there is no error detection: unterminated string, regex and comment
literals are treated as regular javascript code and minified as such.
- Control characters inside string and regex literals are left untouched; they
are not converted to spaces (nor to \n)
- Newline characters are not allowed inside string and regex literals, except
for line continuations in string literals (ECMA-5).
- "return /regex/" is recognized correctly.
- rjsmin does not handle streams, but only complete strings. (However, the
module provides a "streamy" interface).
Besides the list above it differs from direct python ports of jsmin.c in
speed. Since most parts of the logic are handled by the regex engine it's way
faster than the original python port by Baruch Even. The speed factor varies
between about 6 and 55 depending on input and python version (it gets faster
the more compressed the input already is). Compared to the speed-refactored
python port by Dave St.Germain the performance gain is less dramatic but still
between 1.2 and 7. See the docs/BENCHMARKS file for details.
rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more.
Both python 2 and python 3 are supported.
.. _jsmin.c by Douglas Crockford:
http://www.crockford.com/javascript/jsmin.c
Original author of Python version: Andr\xe9 Malo
Home page: http://opensource.perlig.de/rjsmin/
Modified by Ross Peoples <ross.peoples@gmail.com> for inclusion into web2py.
"""
__author__ = "Andr\xe9 Malo"
__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1')
__docformat__ = "restructuredtext en"
__license__ = "Apache License, Version 2.0"
__version__ = '1.0.2'
__all__ = ['jsmin', 'jsmin_for_posers']
import re as _re
def _make_jsmin(extended=True, python_only=True):
"""
Generate JS minifier based on `jsmin.c by Douglas Crockford`_
.. _jsmin.c by Douglas Crockford:
http://www.crockford.com/javascript/jsmin.c
:Parameters:
`extended` : ``bool``
Extended Regexps? (using lookahead and lookbehind). This is faster,
because it can be optimized way more. The regexps used with `extended`
being false are only left here to allow easier porting to platforms
without extended regex features (and for my own reference...)
`python_only` : ``bool``
Use only the python variant. If true, the c extension is not even
tried to be loaded.
:Return: Minifier
:Rtype: ``callable``
"""
# pylint: disable = R0912, R0914, W0612
if not python_only:
try:
import _rjsmin
except ImportError:
pass
else:
return _rjsmin.jsmin
try:
xrange
except NameError:
xrange = range # pylint: disable = W0622
space_chars = r'[\000-\011\013\014\016-\040]'
line_comment = r'(?://[^\r\n]*)'
space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
string1 = \
r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)'
string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")'
strings = r'(?:%s|%s)' % (string1, string2)
charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])'
nospecial = r'[^/\\\[\r\n]'
if extended:
regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % (
nospecial, charclass, nospecial
)
else:
regex = (
r'(?:/(?:[^*/\\\r\n\[]|%s|\\[^\r\n])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)'
)
regex = regex % (charclass, nospecial, charclass, nospecial)
space = r'(?:%s|%s)' % (space_chars, space_comment)
newline = r'(?:%s?[\r\n])' % line_comment
def fix_charclass(result):
""" Fixup string of chars to fit into a regex char class """
pos = result.find('-')
if pos >= 0:
result = r'%s%s-' % (result[:pos], result[pos + 1:])
def sequentize(string):
"""
Notate consecutive characters as sequence
(1-4 instead of 1234)
"""
first, last, result = None, None, []
for char in map(ord, string):
if last is None:
first = last = char
elif last + 1 == char:
last = char
else:
result.append((first, last))
first = last = char
if last is not None:
result.append((first, last))
return ''.join(['%s%s%s' % (
chr(first),
last > first + 1 and '-' or '',
last != first and chr(last) or ''
) for first, last in result])
return _re.sub(r'([\000-\040\047])', # for better portability
lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result)
.replace('\\', '\\\\')
.replace('[', '\\[')
.replace(']', '\\]')
)
)
def id_literal_(what):
""" Make id_literal like char class """
match = _re.compile(what).match
result = ''.join([
chr(c) for c in xrange(127) if not match(chr(c))
])
return '[^%s]' % fix_charclass(result)
def not_id_literal_(keep):
""" Make negated id_literal like char class """
match = _re.compile(id_literal_(keep)).match
result = ''.join([
chr(c) for c in xrange(127) if not match(chr(c))
])
return r'[%s]' % fix_charclass(result)
not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]')
preregex1 = r'[(,=:\[!&|?{};\r\n]'
preregex2 = r'%(not_id_literal)sreturn' % locals()
if extended:
id_literal = id_literal_(r'[a-zA-Z0-9_$]')
id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(+-]')
id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
space_sub = _re.compile((
r'([^\047"/\000-\040]+)'
r'|(%(strings)s[^\047"/\000-\040]*)'
r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
r'|(?<=%(id_literal_close)s)'
r'%(space)s*(?:(%(newline)s)%(space)s*)+'
r'(?=%(id_literal_open)s)'
r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
r'|%(space)s+'
r'|(?:%(newline)s%(space)s*)+'
) % locals()).sub
def space_subber(match):
""" Substitution callback """
# pylint: disable = C0321, R0911
groups = match.groups()
if groups[0]: return groups[0]
elif groups[1]: return groups[1]
elif groups[2]: return groups[2]
elif groups[3]: return groups[3]
elif groups[4]: return '\n'
elif groups[5]: return ' '
else: return ''
def jsmin(script): # pylint: disable = W0621
r"""
Minify javascript based on `jsmin.c by Douglas Crockford`_\.
Instead of parsing the stream char by char, it uses a regular
expression approach which minifies the whole script with one big
substitution regex.
.. _jsmin.c by Douglas Crockford:
http://www.crockford.com/javascript/jsmin.c
:Parameters:
`script` : ``str``
Script to minify
:Return: Minified script
:Rtype: ``str``
"""
return space_sub(space_subber, '\n%s\n' % script).strip()
else:
pre_regex = r'(?:%(preregex1)s|%(preregex2)s)' % locals()
not_id_literal_open = not_id_literal_(r'[a-zA-Z0-9_${\[(+-]')
not_id_literal_close = not_id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
space_norm_sub = _re.compile((
r'(%(strings)s)'
r'|(?:(%(pre_regex)s)%(space)s*(%(regex)s))'
r'|(%(space)s)+'
r'|(?:(%(newline)s)%(space)s*)+'
) % locals()).sub
def space_norm_subber(match):
""" Substitution callback """
# pylint: disable = C0321
groups = match.groups()
if groups[0]: return groups[0]
elif groups[1]: return groups[1].replace('\r', '\n') + groups[2]
elif groups[3]: return ' '
elif groups[4]: return '\n'
space_sub1 = _re.compile((
r'[\040\n]?(%(strings)s|%(pre_regex)s%(regex)s)'
r'|\040(%(not_id_literal)s)'
r'|\n(%(not_id_literal_open)s)'
) % locals()).sub
def space_subber1(match):
""" Substitution callback """
groups = match.groups()
return groups[0] or groups[1] or groups[2]
space_sub2 = _re.compile((
r'(%(strings)s)\040?'
r'|(%(pre_regex)s%(regex)s)[\040\n]?'
r'|(%(not_id_literal)s)\040'
r'|(%(not_id_literal_close)s)\n'
) % locals()).sub
def space_subber2(match):
""" Substitution callback """
groups = match.groups()
return groups[0] or groups[1] or groups[2] or groups[3]
def jsmin(script):
r"""
Minify javascript based on `jsmin.c by Douglas Crockford`_\.
Instead of parsing the stream char by char, it uses a regular
expression approach. The script is minified with three passes:
normalization
Control character are mapped to spaces, spaces and newlines
are squeezed and comments are stripped.
space removal 1
Spaces before certain tokens are removed
space removal 2
Spaces after certain tokens are remove
.. _jsmin.c by Douglas Crockford:
http://www.crockford.com/javascript/jsmin.c
:Parameters:
`script` : ``str``
Script to minify
:Return: Minified script
:Rtype: ``str``
"""
return space_sub2(space_subber2,
space_sub1(space_subber1,
space_norm_sub(space_norm_subber, '\n%s\n' % script)
)
).strip()
return jsmin
jsmin = _make_jsmin()
#####################
# EXAMPLE USAGE #
#####################
#
# import jsmin
# jsmin.jsmin(script)
#
def jsmin_for_posers(script):
r"""
Minify javascript based on `jsmin.c by Douglas Crockford`_\.
Instead of parsing the stream char by char, it uses a regular
expression approach which minifies the whole script with one big
substitution regex.
.. _jsmin.c by Douglas Crockford:
http://www.crockford.com/javascript/jsmin.c
:Warning: This function is the digest of a _make_jsmin() call. It just
utilizes the resulting regex. It's just for fun here and may
vanish any time. Use the `jsmin` function instead.
:Parameters:
`script` : ``str``
Script to minify
:Return: Minified script
:Rtype: ``str``
"""
def subber(match):
""" Substitution callback """
groups = match.groups()
return (
groups[0] or
groups[1] or
groups[2] or
groups[3] or
(groups[4] and '\n') or
(groups[5] and ' ') or
''
)
return _re.sub(
r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-#%-\04'
r'7)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011'
r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-'
r'#%-,./:-@\[-^`{-~-])|(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*'
r'+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011'
r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+',
subber, '\n%s\n' % script
).strip()
if __name__ == '__main__':
import sys as _sys
_sys.stdout.write(jsmin(_sys.stdin.read()))
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
High-level CSS and JS minification class for web2py.
Called by response.include_files()
Created by: Ross Peoples <ross.peoples@gmail.com>
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
"""
import cssmin
import jsmin
import os
def read_binary_file(filename):
f = open(filename,'rb')
data = f.read()
f.close()
return data
def write_binary_file(filename,data):
f =open(filename,'wb')
f.write(data)
f.close()
def fix_links(css,static_path):
return css.replace('../',static_path+'/')
def minify(files, path_info, folder, optimize_css, optimize_js,
ignore_concat = [],
ignore_minity = ['/jquery.js', '/anytime.js']):
"""
Input:
files: is a list of URLs to JS and CSS files (not repeated)
path_info: is the URL of a temp static folder
folder: is the application folder
optimize_css: is a string of the form 'concat|minify|inline'
optimize_js: is a string of the form 'concat|minify|inline'
(minify requires concat, inline requires concat also)
Returns a new list of:
- filename (absolute or relative, css or js, actual or temporary) or
- ('css:inline','...css..')
- ('js:inline','...js..')
"""
concat_css = 'concat' in optimize_css
minify_css = 'minify' in optimize_css
inline_css = 'inline' in optimize_css
concat_js = 'concat' in optimize_js
minify_js = 'minify' in optimize_js
inline_js = 'inline' in optimize_js
static_path,temp = path_info.rsplit('/',1)
new_files = []
css = []
js = []
for k,filename in enumerate(files):
if not filename.startswith('/') or \
any(filename.endswith(x) for x in ignore_concat):
new_files.append(filename)
continue
abs_filename = os.path.join(folder,'static',
filename[len(static_path)+1:])
if filename.lower().endswith('.css'):
if concat_css:
contents = read_binary_file(abs_filename)
if minify_css:
css.append(cssmin.cssmin(contents))
else:
css.append(contents)
else:
css.append(filename)
elif filename.lower().endswith('.js'):
if concat_js:
contents = read_binary_file(abs_filename)
if minify_js and not filename.endswith('.min.js') and \
not any(filename.endswith(x) for x in ignore_minify):
js.append(jsmin.jsmin(contents))
else:
js.append(contents)
else:
js.append(filename)
if css and concat_css:
css = '\n\n'.join(contents for contents in css)
if inline_css:
css = ('css:inline',fix_links(css,static_path))
else:
temppath = os.path.join(folder,'static',temp)
if not os.path.exists(temppath): os.mkdir(temppath)
tempfile = os.path.join(temppath,'compressed.css')
write_binary_file(tempfile,css)
css = path_info+'/compressed.css'
new_files.append(css)
else:
new_files += css
if js and concat_js:
js = '\n'.join(contents for contents in js)
if inline_js:
js = ('js:inline',js)
else:
temppath = os.path.join(folder,'static',temp)
if not os.path.exists(temppath): os.mkdir(temppath)
tempfile = os.path.join(folder,'static',temp,'compressed.js')
write_binary_file(tempfile,js)
js = path_info+'/compressed.js'
new_files.append(js)
else:
new_files += js
return new_files
+125
View File
@@ -0,0 +1,125 @@
# (c) 2007 Chris AtLee <chris@atlee.ca>
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
"""
PAM module for python
Provides an authenticate function that will allow the caller to authenticate
a user against the Pluggable Authentication Modules (PAM) on the system.
Implemented using ctypes, so no compilation is necessary.
"""
__all__ = ['authenticate']
from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof
from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int
from ctypes.util import find_library
LIBPAM = CDLL(find_library("pam"))
LIBC = CDLL(find_library("c"))
CALLOC = LIBC.calloc
CALLOC.restype = c_void_p
CALLOC.argtypes = [c_uint, c_uint]
STRDUP = LIBC.strdup
STRDUP.argstypes = [c_char_p]
STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!!
# Various constants
PAM_PROMPT_ECHO_OFF = 1
PAM_PROMPT_ECHO_ON = 2
PAM_ERROR_MSG = 3
PAM_TEXT_INFO = 4
class PamHandle(Structure):
"""wrapper class for pam_handle_t"""
_fields_ = [
("handle", c_void_p)
]
def __init__(self):
Structure.__init__(self)
self.handle = 0
class PamMessage(Structure):
"""wrapper class for pam_message structure"""
_fields_ = [
("msg_style", c_int),
("msg", c_char_p),
]
def __repr__(self):
return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
class PamResponse(Structure):
"""wrapper class for pam_response structure"""
_fields_ = [
("resp", c_char_p),
("resp_retcode", c_int),
]
def __repr__(self):
return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
CONV_FUNC = CFUNCTYPE(c_int,
c_int, POINTER(POINTER(PamMessage)),
POINTER(POINTER(PamResponse)), c_void_p)
class PamConv(Structure):
"""wrapper class for pam_conv structure"""
_fields_ = [
("conv", CONV_FUNC),
("appdata_ptr", c_void_p)
]
PAM_START = LIBPAM.pam_start
PAM_START.restype = c_int
PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv),
POINTER(PamHandle)]
PAM_AUTHENTICATE = LIBPAM.pam_authenticate
PAM_AUTHENTICATE.restype = c_int
PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
def authenticate(username, password, service='login'):
"""Returns True if the given username and password authenticate for the
given service. Returns False otherwise
``username``: the username to authenticate
``password``: the password in plain text
``service``: the PAM service to authenticate against.
Defaults to 'login'"""
@CONV_FUNC
def my_conv(n_messages, messages, p_response, app_data):
"""Simple conversation function that responds to any
prompt where the echo is off with the supplied password"""
# Create an array of n_messages response objects
addr = CALLOC(n_messages, sizeof(PamResponse))
p_response[0] = cast(addr, POINTER(PamResponse))
for i in range(n_messages):
if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
pw_copy = STRDUP(str(password))
p_response.contents[i].resp = cast(pw_copy, c_char_p)
p_response.contents[i].resp_retcode = 0
return 0
handle = PamHandle()
conv = PamConv(my_conv, 0)
retval = PAM_START(service, username, pointer(conv), pointer(handle))
if retval != 0:
# TODO: This is not an authentication error, something
# has gone wrong starting up PAM
return False
retval = PAM_AUTHENTICATE(handle, 0)
return retval == 0
if __name__ == "__main__":
import getpass
print authenticate(getpass.getuser(), getpass.getpass())
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
Read more about this http://code.google.com/p/pyfpdf
+5
View File
@@ -0,0 +1,5 @@
from fpdf import FPDF
from html import HTMLMixin
from template import Template
+736
View File
@@ -0,0 +1,736 @@
#!/usr/bin/python
# -*- coding: latin-1 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Visual Template designer for PyFPDF (using wxPython OGL library)"
__author__ = "Mariano Reingart <reingart@gmail.com>"
__copyright__ = "Copyright (C) 2011 Mariano Reingart"
__license__ = "GPL 3.0"
__version__ = "1.01a"
# Based on:
# * pySjetch.py wxPython sample application
# * OGL.py and other wxPython demo modules
import os, sys
import wx
import wx.lib.ogl as ogl
from wx.lib.wordwrap import wordwrap
DEBUG = True
class CustomDialog(wx.Dialog):
"A dinamyc dialog to ask user about arbitrary fields"
def __init__(
self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE, fields=None, data=None,
):
wx.Dialog.__init__ (self, parent, ID, title, pos, size, style)
sizer = wx.BoxSizer(wx.VERTICAL)
self.textctrls = {}
for field in fields:
box = wx.BoxSizer(wx.HORIZONTAL)
label = wx.StaticText(self, -1, field)
label.SetHelpText("This is the help text for the label")
box.Add(label, 1, wx.ALIGN_CENTRE|wx.ALL, 5)
text = wx.TextCtrl(self, -1, "", size=(80,-1))
text.SetHelpText("Here's some help text for field #1")
if field in data:
text.SetValue(repr(data[field]))
box.Add(text, 1, wx.ALIGN_CENTRE|wx.ALL, 1)
sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 1)
self.textctrls[field] = text
line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL)
sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5)
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(self, wx.ID_OK)
btn.SetHelpText("The OK button completes the dialog")
btn.SetDefault()
btnsizer.AddButton(btn)
btn = wx.Button(self, wx.ID_CANCEL)
btn.SetHelpText("The Cancel button cancels the dialog. (Cool, huh?)")
btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
@classmethod
def do_input(Class, parent, title, fields, data):
dlg = Class(parent, -1, title, size=(350, 200),
style=wx.DEFAULT_DIALOG_STYLE, # & ~wx.CLOSE_BOX,
fields=fields, data=data
)
dlg.CenterOnScreen()
while 1:
val = dlg.ShowModal()
if val == wx.ID_OK:
values = {}
for field in fields:
try:
values[field] = eval(dlg.textctrls[field].GetValue())
except Exception, e:
msg = wx.MessageDialog(parent, unicode(e),
"Error in field %s" % field,
wx.OK | wx.ICON_INFORMATION
)
msg.ShowModal()
msg.Destroy()
break
else:
return dict([(field, values[field]) for field in fields])
else:
return None
class MyEvtHandler(ogl.ShapeEvtHandler):
"Custom Event Handler for Shapes"
def __init__(self, callback):
ogl.ShapeEvtHandler.__init__(self)
self.callback = callback
def OnLeftClick(self, x, y, keys=0, attachment=0):
shape = self.GetShape()
canvas = shape.GetCanvas()
dc = wx.ClientDC(canvas)
canvas.PrepareDC(dc)
if shape.Selected() and keys & ogl.KEY_SHIFT:
shape.Select(False, dc)
#canvas.Redraw(dc)
canvas.Refresh(False)
else:
redraw = False
shapeList = canvas.GetDiagram().GetShapeList()
toUnselect = []
for s in shapeList:
if s.Selected() and not keys & ogl.KEY_SHIFT:
# If we unselect it now then some of the objects in
# shapeList will become invalid (the control points are
# shapes too!) and bad things will happen...
toUnselect.append(s)
shape.Select(True, dc)
if toUnselect:
for s in toUnselect:
s.Select(False, dc)
##canvas.Redraw(dc)
canvas.Refresh(False)
self.callback()
def OnEndDragLeft(self, x, y, keys=0, attachment=0):
shape = self.GetShape()
ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
if not shape.Selected():
self.OnLeftClick(x, y, keys, attachment)
self.callback()
def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
self.callback()
def OnMovePost(self, dc, x, y, oldX, oldY, display):
shape = self.GetShape()
ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display)
self.callback()
if "wxMac" in wx.PlatformInfo:
shape.GetCanvas().Refresh(False)
def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
self.callback("LeftDoubleClick")
def OnRightClick(self, *dontcare):
self.callback("RightClick")
class Element(object):
"Visual class that represent a placeholder in the template"
fields = ['name', 'type',
'x1', 'y1', 'x2', 'y2',
'font', 'size',
'bold', 'italic', 'underline',
'foreground', 'background',
'align', 'text', 'priority',]
def __init__(self, canvas=None, frame=None, zoom=5.0, static=False, **kwargs):
self.kwargs = kwargs
self.zoom = zoom
self.frame = frame
self.canvas = canvas
self.static = static
name = kwargs['name']
kwargs['type']
type = kwargs['type']
x, y, w, h = self.set_coordinates(kwargs['x1'], kwargs['y1'], kwargs['x2'], kwargs['y2'])
text = kwargs['text']
shape = self.shape = ogl.RectangleShape(w, h)
if not static:
shape.SetDraggable(True, True)
shape.SetX(x)
shape.SetY(y)
#if pen: shape.SetPen(pen)
#if brush: shape.SetBrush(brush)
shape.SetBrush(wx.TRANSPARENT_BRUSH)
if type not in ('L', 'B', 'BC'):
if not static:
pen = wx.LIGHT_GREY_PEN
else:
pen = wx.RED_PEN
shape.SetPen(pen)
self.text = kwargs['text']
evthandler = MyEvtHandler(self.evt_callback)
evthandler.SetShape(shape)
evthandler.SetPreviousHandler(shape.GetEventHandler())
shape.SetEventHandler(evthandler)
shape.SetCentreResize(False)
shape.SetMaintainAspectRatio(False)
canvas.AddShape( shape )
@classmethod
def new(Class, parent):
data = dict(name='some_name', type='T',
x1=5.0, y1=5.0, x2=100.0, y2=10.0,
font="Arial", size=12,
bold=False, italic=False, underline=False,
foreground= 0x000000, background=0xFFFFFF,
align="L", text="", priority=0)
data = CustomDialog.do_input(parent, 'New element', Class.fields, data)
if data:
return Class(canvas=parent.canvas, frame=parent, **data)
def edit(self):
"Edit current element (show a dialog box with all fields)"
data = self.kwargs.copy()
x1, y1, x2, y2 = self.get_coordinates()
data.update(dict(name=self.name,
text=self.text,
x1=x1, y1=y1, x2=x2, y2=y2,
))
data = CustomDialog.do_input(self.frame, 'Edit element', self.fields, data)
if data:
self.kwargs.update(data)
self.name = data['name']
self.text = data['text']
x,y, w, h = self.set_coordinates(data['x1'], data['y1'], data['x2'], data['y2'])
self.shape.SetX(x)
self.shape.SetY(y)
self.shape.SetWidth(w)
self.shape.SetHeight(h)
self.canvas.Refresh(False)
self.canvas.GetDiagram().ShowAll(1)
def edit_text(self):
"Allow text edition (i.e. for doubleclick)"
dlg = wx.TextEntryDialog(
self.frame, 'Text for %s' % self.name,
'Edit Text', '')
if self.text:
dlg.SetValue(self.text)
if dlg.ShowModal() == wx.ID_OK:
self.text = dlg.GetValue().encode("latin1")
dlg.Destroy()
def copy(self):
"Return an identical duplicate"
kwargs = self.as_dict()
element = Element(canvas=self.canvas, frame=self.frame, zoom=self.zoom, static=self.static, **kwargs)
return element
def remove(self):
"Erases visual shape from OGL canvas (element must be deleted manually)"
self.canvas.RemoveShape(self.shape)
def move(self, dx, dy):
"Change pdf coordinates (converting to wx internal values)"
x1, y1, x2, y2 = self.get_coordinates()
x1 += dx
x2 += dx
y1 += dy
y2 += dy
x, y, w, h = self.set_coordinates(x1, y1, x2, y2)
self.shape.SetX(x)
self.shape.SetY(y)
def evt_callback(self, evt_type=None):
"Event dispatcher"
if evt_type=="LeftDoubleClick":
self.edit_text()
if evt_type=='RightClick':
self.edit()
# update the status bar
x1, y1, x2, y2 = self.get_coordinates()
self.frame.SetStatusText("%s (%0.2f, %0.2f) - (%0.2f, %0.2f)" %
(self.name, x1, y1, x2, y2))
def get_coordinates(self):
"Convert from wx to pdf coordinates"
x, y = self.shape.GetX(), self.shape.GetY()
w, h = self.shape.GetBoundingBoxMax()
w -= 1
h -= 1
x1 = x/self.zoom - w/self.zoom/2.0
x2 = x/self.zoom + w/self.zoom/2.0
y1 = y/self.zoom - h/self.zoom/2.0
y2 = y/self.zoom + h/self.zoom/2.0
return x1, y1, x2, y2
def set_coordinates(self, x1, y1, x2, y2):
"Convert from pdf to wx coordinates"
x1 = x1 * self.zoom
x2 = x2 * self.zoom
y1 = y1 * self.zoom
y2 = y2 * self.zoom
# shapes seems to be centred, pdf coord not
w = max(x1, x2) - min(x1, x2) + 1
h = max(y1, y2) - min(y1, y2) + 1
x = (min(x1, x2) + w/2.0)
y = (min(y1, y2) + h/2.0)
return x, y, w, h
def text(self, txt=None):
if txt is not None:
if not isinstance(txt,str):
txt = str(txt)
self.kwargs['text'] = txt
self.shape.ClearText()
for line in txt.split('\n'):
self.shape.AddText(unicode(line, "latin1"))
self.canvas.Refresh(False)
return self.kwargs['text']
text = property(text, text)
def set_x(self, x):
self.shape.SetX(x)
self.canvas.Refresh(False)
self.evt_callback()
def set_y(self, y):
self.shape.SetY(y)
self.canvas.Refresh(False)
self.evt_callback()
def get_x(self):
return self.shape.GetX()
def get_y(self):
return self.shape.GetY()
x = property(get_x, set_x)
y = property(get_y, set_y)
def selected(self, sel=None):
if sel is not None:
print "Setting Select(%s)" % sel
self.shape.Select(sel)
return self.shape.Selected()
selected = property(selected, selected)
def name(self, name=None):
if name is not None:
self.kwargs['name'] = name
return self.kwargs['name']
name = property(name, name)
def __contains__(self, k):
"Implement in keyword for searchs"
return k in self.name.lower() or self.text and k in self.text.lower()
def as_dict(self):
"Return a dictionary representation, used by pyfpdf"
d = self.kwargs
x1, y1, x2, y2 = self.get_coordinates()
d.update({
'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2,
'text': self.text})
return d
class AppFrame(wx.Frame):
"OGL Designer main window"
title = "PyFPDF Template Designer (wx OGL)"
def __init__(self):
wx.Frame.__init__( self,
None, -1, self.title,
size=(640,480),
style=wx.DEFAULT_FRAME_STYLE )
sys.excepthook = self.except_hook
self.filename = ""
# Create a toolbar:
tsize = (16,16)
self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT)
artBmp = wx.ArtProvider.GetBitmap
self.toolbar.AddSimpleTool(
wx.ID_NEW, artBmp(wx.ART_NEW, wx.ART_TOOLBAR, tsize), "New")
self.toolbar.AddSimpleTool(
wx.ID_OPEN, artBmp(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize), "Open")
self.toolbar.AddSimpleTool(
wx.ID_SAVE, artBmp(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize), "Save")
self.toolbar.AddSimpleTool(
wx.ID_SAVEAS, artBmp(wx.ART_FILE_SAVE_AS, wx.ART_TOOLBAR, tsize),
"Save As...")
#-------
self.toolbar.AddSeparator()
self.toolbar.AddSimpleTool(
wx.ID_UNDO, artBmp(wx.ART_UNDO, wx.ART_TOOLBAR, tsize), "Undo")
self.toolbar.AddSimpleTool(
wx.ID_REDO, artBmp(wx.ART_REDO, wx.ART_TOOLBAR, tsize), "Redo")
self.toolbar.AddSeparator()
#-------
self.toolbar.AddSimpleTool(
wx.ID_CUT, artBmp(wx.ART_CUT, wx.ART_TOOLBAR, tsize), "Remove")
self.toolbar.AddSimpleTool(
wx.ID_COPY, artBmp(wx.ART_COPY, wx.ART_TOOLBAR, tsize), "Duplicate")
self.toolbar.AddSimpleTool(
wx.ID_PASTE, artBmp(wx.ART_PASTE, wx.ART_TOOLBAR, tsize), "Insert")
self.toolbar.AddSeparator()
self.toolbar.AddSimpleTool(
wx.ID_FIND, artBmp(wx.ART_FIND, wx.ART_TOOLBAR, tsize), "Find")
self.toolbar.AddSeparator()
self.toolbar.AddSimpleTool(
wx.ID_PRINT, artBmp(wx.ART_PRINT, wx.ART_TOOLBAR, tsize), "Print")
self.toolbar.AddSimpleTool(
wx.ID_ABOUT, artBmp(wx.ART_HELP, wx.ART_TOOLBAR, tsize), "About")
self.toolbar.Realize()
self.toolbar.EnableTool(wx.ID_SAVEAS, False)
self.toolbar.EnableTool(wx.ID_UNDO, False)
self.toolbar.EnableTool(wx.ID_REDO, False)
menu_handlers = [
(wx.ID_NEW, self.do_new),
(wx.ID_OPEN, self.do_open),
(wx.ID_SAVE, self.do_save),
(wx.ID_PRINT, self.do_print),
(wx.ID_FIND, self.do_find),
(wx.ID_CUT, self.do_cut),
(wx.ID_COPY, self.do_copy),
(wx.ID_PASTE, self.do_paste),
(wx.ID_ABOUT, self.do_about),
]
for menu_id, handler in menu_handlers:
self.Bind(wx.EVT_MENU, handler, id = menu_id)
sizer = wx.BoxSizer(wx.VERTICAL)
# put stuff into sizer
self.CreateStatusBar()
canvas = self.canvas = ogl.ShapeCanvas( self )
maxWidth = 1500
maxHeight = 2000
canvas.SetScrollbars(20, 20, maxWidth/20, maxHeight/20)
sizer.Add( canvas, 1, wx.GROW )
canvas.SetBackgroundColour("WHITE") #
diagram = self.diagram = ogl.Diagram()
canvas.SetDiagram( diagram )
diagram.SetCanvas( canvas )
diagram.SetSnapToGrid( False )
# apply sizer
self.SetSizer(sizer)
self.SetAutoLayout(1)
self.Show(1)
self.Bind(wx.EVT_CHAR_HOOK, self.on_key_event)
self.elements = []
def on_key_event(self, event):
""" Respond to a keypress event.
We make the arrow keys move the selected object(s) by one pixel in
the given direction.
"""
step = 1
if event.ControlDown():
step = 20
if event.GetKeyCode() == wx.WXK_UP:
self.move_elements(0, -step)
elif event.GetKeyCode() == wx.WXK_DOWN:
self.move_elements(0, step)
elif event.GetKeyCode() == wx.WXK_LEFT:
self.move_elements(-step, 0)
elif event.GetKeyCode() == wx.WXK_RIGHT:
self.move_elements(step, 0)
elif event.GetKeyCode() == wx.WXK_DELETE:
self.do_cut()
else:
event.Skip()
def do_new(self, evt=None):
for element in self.elements:
element.remove()
self.elements = []
# draw paper size guides
for k, (w, h) in [('legal', (216, 356)), ('A4', (210, 297)), ('letter', (216, 279))]:
self.create_elements(
k, 'R', 0, 0, w, h,
size=70, foreground=0x808080, priority=-100,
canvas=self.canvas, frame=self, static=True)
self.diagram.ShowAll( 1 )
def do_open(self, evt):
dlg = wx.FileDialog(
self, message="Choose a file",
defaultDir=os.getcwd(),
defaultFile="invoice.csv",
wildcard="CSV Files (*.csv)|*.csv",
style=wx.OPEN
)
if dlg.ShowModal() == wx.ID_OK:
# This returns a Python list of files that were selected.
self.filename = dlg.GetPaths()[0]
dlg.Destroy()
self.SetTitle(self.filename + " - " + self.title)
self.do_new()
tmp = []
f = open(self.filename)
try:
filedata = f.readlines()
finally:
f.close()
for lno, linea in enumerate(filedata):
if DEBUG: print "processing line", lno, linea
args = []
for i,v in enumerate(linea.split(";")):
if not v.startswith("'"):
v = v.replace(",",".")
else:
v = v#.decode('latin1')
if v.strip()=='':
v = None
else:
v = eval(v.strip())
args.append(v)
tmp.append(args)
# sort by z-order (priority)
for args in sorted(tmp, key=lambda t: t[-1]):
if DEBUG: print args
self.create_elements(*args)
self.diagram.ShowAll( 1 ) #
return True
def do_save(self, evt, filename=None):
try:
from time import gmtime, strftime
ts = strftime("%Y%m%d%H%M%S", gmtime())
os.rename(self.filename, self.filename + ts + ".bak")
except Exception, e:
if DEBUG: print e
pass
def csv_repr(v, decimal_sep="."):
if isinstance(v, float):
return ("%0.2f" % v).replace(".", decimal_sep)
else:
return repr(v)
f = open(self.filename, "w")
try:
for element in sorted(self.elements, key=lambda e:e.name):
if element.static:
continue
d = element.as_dict()
l = [d['name'], d['type'],
d['x1'], d['y1'], d['x2'], d['y2'],
d['font'], d['size'],
d['bold'], d['italic'], d['underline'],
d['foreground'], d['background'],
d['align'], d['text'], d['priority'],
]
f.write(";".join([csv_repr(v) for v in l]))
f.write("\n")
finally:
f.close()
def do_print(self, evt):
# genero el renderizador con propiedades del PDF
from template import Template
t = Template(elements=[e.as_dict() for e in self.elements if not e.static])
t.add_page()
if not t['logo'] or not os.path.exists(t['logo']):
# put a default logo so it doesn't trow an exception
logo = os.path.join(os.path.dirname(__file__), 'tutorial','logo.png')
t.set('logo', logo)
try:
t.render(self.filename +".pdf")
except:
if DEBUG and False:
import pdb;
pdb.pm()
else:
raise
if sys.platform=="linux2":
os.system("evince ""%s""" % self.filename +".pdf")
else:
os.startfile(self.filename +".pdf")
def do_find(self, evt):
# busco nombre o texto
dlg = wx.TextEntryDialog(
self, 'Enter text to search for',
'Find Text', '')
if dlg.ShowModal() == wx.ID_OK:
txt = dlg.GetValue().encode("latin1").lower()
for element in self.elements:
if txt in element:
element.selected = True
print "Found:", element.name
self.canvas.Refresh(False)
dlg.Destroy()
def do_cut(self, evt=None):
"Delete selected elements"
new_elements = []
for element in self.elements:
if element.selected:
print "Erasing:", element.name
element.selected = False
self.canvas.Refresh(False)
element.remove()
else:
new_elements.append(element)
self.elements = new_elements
self.canvas.Refresh(False)
self.diagram.ShowAll( 1 )
def do_copy(self, evt):
"Duplicate selected elements"
fields = ['qty', 'dx', 'dy']
data = {'qty': 1, 'dx': 0.0, 'dy': 5.0}
data = CustomDialog.do_input(self, 'Copy elements', fields, data)
if data:
new_elements = []
for i in range(1, data['qty']+1):
for element in self.elements:
if element.selected:
print "Copying:", element.name
new_element = element.copy()
name = new_element.name
if len(name)>2 and name[-2:].isdigit():
new_element.name = name[:-2] + "%02d" % (int(name[-2:])+i)
else:
new_element.name = new_element.name + "_copy"
new_element.selected = False
new_element.move(data['dx']*i, data['dy']*i)
new_elements.append(new_element)
self.elements.extend(new_elements)
self.canvas.Refresh(False)
self.diagram.ShowAll( 1 )
def do_paste(self, evt):
"Insert new elements"
element = Element.new(self)
if element:
self.canvas.Refresh(False)
self.elements.append(element)
self.diagram.ShowAll( 1 )
def create_elements(self, name, type, x1, y1, x2, y2,
font="Arial", size=12,
bold=False, italic=False, underline=False,
foreground= 0x000000, background=0xFFFFFF,
align="L", text="", priority=0, canvas=None, frame=None, static=False,
**kwargs):
element = Element(name=name, type=type, x1=x1, y1=y1, x2=x2, y2=y2,
font=font, size=size,
bold=bold, italic=italic, underline=underline,
foreground= foreground, background=background,
align=align, text=text, priority=priority,
canvas=canvas or self.canvas, frame=frame or self,
static=static)
self.elements.append(element)
def move_elements(self, x, y):
for element in self.elements:
if element.selected:
print "moving", element.name, x, y
element.x = element.x + x
element.y = element.y + y
def do_about(self, evt):
info = wx.AboutDialogInfo()
info.Name = self.title
info.Version = __version__
info.Copyright = __copyright__
info.Description = (
"Visual Template designer for PyFPDF (using wxPython OGL library)\n"
"Input files are CSV format describing the layout, separated by ;\n"
"Use toolbar buttons to open, save, print (preview) your template, "
"and there are buttons to find, add, remove or duplicate elements.\n"
"Over an element, a double left click opens edit text dialog, "
"and a right click opens edit properties dialog. \n"
"Multiple element can be selected with shift left click. \n"
"Use arrow keys or drag-and-drop to move elements.\n"
"For further information see project webpage:"
)
info.WebSite = ("http://code.google.com/p/pyfpdf/wiki/Templates",
"pyfpdf Google Code Project")
info.Developers = [ __author__, ]
info.License = wordwrap(__license__, 500, wx.ClientDC(self))
# Then we call wx.AboutBox giving it that info object
wx.AboutBox(info)
def except_hook(self, type, value, trace):
import traceback
exc = traceback.format_exception(type, value, trace)
for e in exc: wx.LogError(e)
wx.LogError('Unhandled Error: %s: %s'%(str(type), str(value)))
app = wx.PySimpleApp()
ogl.OGLInitialize()
frame = AppFrame()
app.MainLoop()
app.Destroy()
File diff suppressed because it is too large Load Diff
+459
View File
@@ -0,0 +1,459 @@
# -*- coding: latin-1 -*-
"HTML Renderer for FPDF.py"
__author__ = "Mariano Reingart <reingart@gmail.com>"
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
__license__ = "LGPL 3.0"
# Inspired by tuto5.py and several examples from fpdf.org, html2fpdf, etc.
from fpdf import FPDF
from HTMLParser import HTMLParser
DEBUG = False
def px2mm(px):
return int(px)*25.4/72.0
def hex2dec(color = "#000000"):
if color:
r = int(color[1:3], 16)
g = int(color[3:5], 16)
b = int(color[5:7], 16)
return r, g, b
class HTML2FPDF(HTMLParser):
"Render basic HTML to FPDF"
def __init__(self, pdf, image_map, **kwargs):
HTMLParser.__init__(self)
self.image_map = image_map
self.style = {}
self.pre = False
self.href = ''
self.align = ''
self.page_links = {}
self.font_list = ("times","courier", "helvetica")
self.pdf = pdf
self.r = self.g = self.b = 0
self.indent = 0
self.bullet = []
self.font_face="times" # initialize font
self.color=0 # initialize font color
self.set_font(kwargs.get("font","times"), kwargs.get("fontsize",12))
self.table = None # table attributes
self.table_col_width = None # column (header) widths
self.table_col_index = None # current column index
self.td = None # cell attributes
self.th = False # header enabled
self.tr = None
self.theader = None # table header cells
self.tfooter = None # table footer cells
self.thead = None
self.tfoot = None
self.theader_out = self.tfooter_out = False
def width2mm(self, length):
if length[-1]=='%':
total = self.pdf.w - self.pdf.r_margin - self.pdf.l_margin
if self.table['width'][-1]=='%':
total *= int(self.table['width'][:-1])/100.0
return int(length[:-1]) * total / 101.0
else:
return int(length) / 6.0
def handle_data(self, txt):
if self.td is not None: # drawing a table?
if 'width' not in self.td and 'colspan' not in self.td:
l = [self.table_col_width[self.table_col_index]]
elif 'colspan' in self.td:
i = self.table_col_index
colspan = int(self.td['colspan'])
l = self.table_col_width[i:i+colspan]
else:
l = [self.td.get('width','240')]
w = sum([self.width2mm(lenght) for lenght in l])
h = int(self.td.get('height', 0)) / 4 or self.h*1.30
self.table_h = h
border = int(self.table.get('border', 0))
if not self.th:
align = self.td.get('align', 'L')[0].upper()
border = border and 'LR'
else:
self.set_style('B',True)
border = border or 'B'
align = 'C'
bgcolor = hex2dec(self.td.get('bgcolor', self.tr.get('bgcolor', '')))
# parsing table header/footer (drawn later):
if self.thead is not None:
self.theader.append(((w,h,txt,border,0,align), bgcolor))
if self.tfoot is not None:
self.tfooter.append(((w,h,txt,border,0,align), bgcolor))
# check if reached end of page, add table footer and header:
height = h + (self.tfooter and self.tfooter[0][0][1] or 0)
if self.pdf.y+height>self.pdf.page_break_trigger and not self.th:
self.output_table_footer()
self.pdf.add_page()
self.theader_out = self.tfooter_out = False
if self.tfoot is None and self.thead is None:
if not self.theader_out:
self.output_table_header()
self.box_shadow(w, h, bgcolor)
if DEBUG: print "td cell", self.pdf.x, w, txt, "*"
self.pdf.cell(w,h,txt,border,0,align)
elif self.table is not None:
# ignore anything else than td inside a table
pass
elif self.align:
if DEBUG: print "cell", txt, "*"
self.pdf.cell(0,self.h,txt,0,1,self.align[0].upper(), self.href)
else:
txt = txt.replace("\n"," ")
if self.href:
self.put_link(self.href,txt)
else:
if DEBUG: print "write", txt, "*"
self.pdf.write(self.h,txt)
def box_shadow(self, w, h, bgcolor):
if DEBUG: print "box_shadow", w, h, bgcolor
if bgcolor:
fill_color = self.pdf.fill_color
self.pdf.set_fill_color(*bgcolor)
self.pdf.rect(self.pdf.x, self.pdf.y, w, h, 'F')
self.pdf.fill_color = fill_color
def output_table_header(self):
if self.theader:
b = self.b
x = self.pdf.x
self.pdf.set_x(self.table_offset)
self.set_style('B',True)
for cell, bgcolor in self.theader:
self.box_shadow(cell[0], cell[1], bgcolor)
self.pdf.cell(*cell)
self.set_style('B',b)
self.pdf.ln(self.theader[0][0][1])
self.pdf.set_x(self.table_offset)
#self.pdf.set_x(x)
self.theader_out = True
def output_table_footer(self):
if self.tfooter:
x = self.pdf.x
self.pdf.set_x(self.table_offset)
#TODO: self.output_table_sep()
for cell, bgcolor in self.tfooter:
self.box_shadow(cell[0], cell[1], bgcolor)
self.pdf.cell(*cell)
self.pdf.ln(self.tfooter[0][0][1])
self.pdf.set_x(x)
if int(self.table.get('border', 0)):
self.output_table_sep()
self.tfooter_out = True
def output_table_sep(self):
self.pdf.set_x(self.table_offset)
x1 = self.pdf.x
y1 = self.pdf.y
w = sum([self.width2mm(lenght) for lenght in self.table_col_width])
self.pdf.line(x1,y1,x1+w,y1)
def handle_starttag(self, tag, attrs):
attrs = dict(attrs)
if DEBUG: print "STARTTAG", tag, attrs
if tag=='b' or tag=='i' or tag=='u':
self.set_style(tag,1)
if tag=='a':
self.href=attrs['href']
if tag=='br':
self.pdf.ln(5)
if tag=='p':
self.pdf.ln(5)
if attrs:
self.align=attrs['align'].lower()
if tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'):
k = (2, 1.5, 1.17, 1, 0.83, 0.67)[int(tag[1])]
self.pdf.ln(5*k)
self.pdf.set_text_color(150,0,0)
self.pdf.set_font_size(12 * k)
if attrs: self.align = attrs.get('align')
if tag=='hr':
self.put_line()
if tag=='pre':
self.pdf.set_font('Courier','',11)
self.pdf.set_font_size(11)
self.set_style('B',False)
self.set_style('I',False)
self.pre = True
if tag=='blockquote':
self.set_text_color(100,0,45)
self.pdf.ln(3)
if tag=='ul':
self.indent+=1
self.bullet.append('\x95')
if tag=='ol':
self.indent+=1
self.bullet.append(0)
if tag=='li':
self.pdf.ln(self.h+2)
self.pdf.set_text_color(190,0,0)
bullet = self.bullet[self.indent-1]
if not isinstance(bullet, basestring):
bullet += 1
self.bullet[self.indent-1] = bullet
bullet = "%s. " % bullet
self.pdf.write(self.h,'%s%s ' % (' '*5*self.indent, bullet))
self.set_text_color()
if tag=='font':
if 'color' in attrs:
self.color = hex2dec(attrs['color'])
self.set_text_color(*color)
self.color = color
if 'face' in attrs and attrs['face'].lower() in self.font_list:
face = attrs.get('face').lower()
self.pdf.set_font(face)
self.font_face = face
if 'size' in attrs:
size = int(attrs.get('size'))
self.pdf.set_font(self.font_face, size=int(size))
self.font_size = size
if tag=='table':
self.table = dict([(k.lower(), v) for k,v in attrs.items()])
if not 'width' in self.table:
self.table['width'] = '100%'
if self.table['width'][-1]=='%':
w = self.pdf.w - self.pdf.r_margin - self.pdf.l_margin
w *= int(self.table['width'][:-1])/100.0
self.table_offset = (self.pdf.w-w)/2.0
self.table_col_width = []
self.theader_out = self.tfooter_out = False
self.theader = []
self.tfooter = []
self.thead = None
self.tfoot = None
self.pdf.ln()
if tag=='tr':
self.tr = dict([(k.lower(), v) for k,v in attrs.items()])
self.table_col_index = 0
self.pdf.set_x(self.table_offset)
if tag=='td':
self.td = dict([(k.lower(), v) for k,v in attrs.items()])
if tag=='th':
self.td = dict([(k.lower(), v) for k,v in attrs.items()])
self.th = True
if self.td['width']:
self.table_col_width.append(self.td['width'])
if tag=='thead':
self.thead = {}
if tag=='tfoot':
self.tfoot = {}
if tag=='img':
if 'src' in attrs:
x = self.pdf.get_x()
y = self.pdf.get_y()
w = px2mm(attrs.get('width', 0))
h = px2mm(attrs.get('height',0))
if self.align and self.align[0].upper() == 'C':
x = (self.pdf.w-x)/2.0 - w/2.0
self.pdf.image(self.image_map(attrs['src']),
x, y, w, h, link=self.href)
self.pdf.set_x(x+w)
self.pdf.set_y(y+h)
if tag=='b' or tag=='i' or tag=='u':
self.set_style(tag, True)
if tag=='center':
self.align = 'Center'
def handle_endtag(self, tag):
#Closing tag
if DEBUG: print "ENDTAG", tag
if tag=='h1' or tag=='h2' or tag=='h3' or tag=='h4':
self.pdf.ln(6)
self.set_font()
self.set_style()
self.align = None
if tag=='pre':
self.pdf.set_font(self.font or 'Times','',12)
self.pdf.set_font_size(12)
self.pre=False
if tag=='blockquote':
self.set_text_color(0,0,0)
self.pdf.ln(3)
if tag=='strong':
tag='b'
if tag=='em':
tag='i'
if tag=='b' or tag=='i' or tag=='u':
self.set_style(tag, False)
if tag=='a':
self.href=''
if tag=='p':
self.align=''
if tag in ('ul', 'ol'):
self.indent-=1
self.bullet.pop()
if tag=='table':
if not self.tfooter_out:
self.output_table_footer()
self.table = None
self.th = False
self.theader = None
self.tfooter = None
self.pdf.ln()
if tag=='thead':
self.thead = None
if tag=='tfoot':
self.tfoot = None
if tag=='tbody':
# draw a line separator between table bodies
self.pdf.set_x(self.table_offset)
self.output_table_sep()
if tag=='tr':
h = self.table_h
if self.tfoot is None:
self.pdf.ln(h)
self.tr = None
if tag=='td' or tag=='th':
if self.th:
if DEBUG: print "revert style"
self.set_style('B', False) # revert style
self.table_col_index += int(self.td.get('colspan','1'))
self.td = None
self.th = False
if tag=='font':
if self.color:
self.pdf.set_text_color(0,0,0)
self.color = None
if self.font_face:
self.set_font('Times',12)
if tag=='center':
self.align = None
def set_font(self, face=None, size=None):
if face:
self.font_face = face
if size:
self.font_size = size
self.h = size / 72.0*25.4
if DEBUG: print "H", self.h
self.pdf.set_font(self.font_face or 'times','',12)
self.pdf.set_font_size(self.font_size or 12)
self.set_style('u', False)
self.set_style('b', False)
self.set_style('i', False)
self.set_text_color()
def set_style(self, tag=None, enable=None):
#Modify style and select corresponding font
if tag:
t = self.style.get(tag.lower())
self.style[tag.lower()] = enable
style=''
for s in ('b','i','u'):
if self.style.get(s):
style+=s
if DEBUG: print "SET_FONT_STYLE", style
self.pdf.set_font('',style)
def set_text_color(self, r=None, g=0, b=0):
if r is None:
self.pdf.set_text_color(self.r,self.g,self.b)
else:
self.pdf.set_text_color(r, g, b)
self.r = r
self.g = g
self.b = b
def put_link(self, url, txt):
#Put a hyperlink
self.set_text_color(0,0,255)
self.set_style('u', True)
self.pdf.write(5,txt,url)
self.set_style('u', False)
self.set_text_color(0)
def put_line(self):
self.pdf.ln(2)
self.pdf.line(self.pdf.get_x(),self.pdf.get_y(),self.pdf.get_x()+187,self.pdf.get_y())
self.pdf.ln(3)
class HTMLMixin():
def write_html(self, text, image_map=lambda x:x, **kwargs):
"Parse HTML and convert it to PDF"
h2p = HTML2FPDF(self,image_map=image_map,**kwargs)
h2p.feed(text)
if __name__=='__main__':
html="""
<H1 align="center">html2fpdf</H1>
<h2>Basic usage</h2>
<p>You can now easily print text mixing different
styles : <B>bold</B>, <I>italic</I>, <U>underlined</U>, or
<B><I><U>all at once</U></I></B>!<BR>You can also insert links
on text, such as <A HREF="http://www.fpdf.org">www.fpdf.org</A>,
or on an image: click on the logo.<br>
<center>
<A HREF="http://www.fpdf.org"><img src="tutorial/logo.png" width="104" height="71"></A>
</center>
<h3>Sample List</h3>
<ul><li>option 1</li>
<ol><li>option 2</li></ol>
<li>option 3</li></ul>
<table border="0" align="center" width="50%">
<thead><tr><th width="30%">Header 1</th><th width="70%">header 2</th></tr></thead>
<tbody>
<tr><td>cell 1</td><td>cell 2</td></tr>
<tr><td>cell 2</td><td>cell 3</td></tr>
</tbody>
</table>
<table border="1">
<thead><tr bgcolor="#A0A0A0"><th width="30%">Header 1</th><th width="70%">header 2</th></tr></thead>
<tfoot><tr bgcolor="#E0E0E0"><td>footer 1</td><td>footer 2</td></tr></tfoot>
<tbody>
<tr><td>cell 1</td><td>cell 2</td></tr>
<tr>
<td width="30%">cell 1</td><td width="70%" bgcolor="#D0D0FF" align='right'>cell 2</td>
</tr>
</tbody>
<tbody><tr><td colspan="2">cell spanned</td></tr></tbody>
<tbody>
""" + """<tr bgcolor="#F0F0F0">
<td>cell 3</td><td>cell 4</td>
</tr><tr bgcolor="#FFFFFF">
<td>cell 5</td><td>cell 6</td>
</tr>""" * 200 + """
</tbody>
</table>
"""
class MyFPDF(FPDF, HTMLMixin):
def header(self):
self.image('tutorial/logo_pb.png',10,8,33)
self.set_font('Arial','B',15)
self.cell(80)
self.cell(30,10,'Title',1,0,'C')
self.ln(20)
def footer(self):
self.set_y(-15)
self.set_font('Arial','I',8)
txt = 'Page %s of %s' % (self.page_no(), self.alias_nb_pages())
self.cell(0,10,txt,0,0,'C')
pdf=MyFPDF()
#First page
pdf.add_page()
pdf.write_html(html)
pdf.output('html.pdf','F')
import os
os.system("evince html.pdf")
+278
View File
@@ -0,0 +1,278 @@
# -*- coding: iso-8859-1 -*-
"PDF Template Helper for FPDF.py"
__author__ = "Mariano Reingart <reingart@gmail.com>"
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
__license__ = "LGPL 3.0"
import sys,os,csv
from fpdf import FPDF
def rgb(col):
return (col // 65536), (col // 256 % 256), (col% 256)
class Template:
def __init__(self, infile=None, elements=None, format='A4', orientation='portrait',
title='', author='', subject='', creator='', keywords=''):
if elements:
self.elements = dict([(v['name'].lower(),v) for v in elements])
self.handlers = {'T': self.text, 'L': self.line, 'I': self.image,
'B': self.rect, 'BC': self.barcode, }
self.pg_no = 0
self.texts = {}
pdf = self.pdf = FPDF(format=format,orientation=orientation, unit="mm")
pdf.set_title(title)
pdf.set_author(author)
pdf.set_creator(creator)
pdf.set_subject(subject)
pdf.set_keywords(keywords)
def parse_csv(self, infile, delimiter=",", decimal_sep="."):
"Parse template format csv file and create elements dict"
keys = ('name','type','x1','y1','x2','y2','font','size',
'bold','italic','underline','foreground','background',
'align','text','priority')
self.elements = {}
f = open(infile, 'rb')
try:
for row in csv.reader(f, delimiter=delimiter):
kargs = {}
for i,v in enumerate(row):
if not v.startswith("'") and decimal_sep!=".":
v = v.replace(decimal_sep,".")
else:
v = v
if v=='':
v = None
else:
v = eval(v.strip())
kargs[keys[i]] = v
self.elements[kargs['name'].lower()] = kargs
finally:
f.close()
def add_page(self):
self.pg_no += 1
self.texts[self.pg_no] = {}
def __setitem__(self, name, value):
if name.lower() in self.elements:
if isinstance(value,unicode):
value = value.encode("latin1","ignore")
else:
value = str(value)
self.texts[self.pg_no][name.lower()] = value
# setitem shortcut (may be further extended)
set = __setitem__
def __getitem__(self, name):
if name.lower() in self.elements:
return self.texts[self.pg_no].get(name.lower(), self.elements[name.lower()]['text'])
def split_multicell(self, text, element_name):
"Divide (\n) a string using a given element width"
pdf = self.pdf
element = self.elements[element_name.lower()]
style = ""
if element['bold']: style += "B"
if element['italic']: style += "I"
if element['underline']: style += "U"
pdf.set_font(element['font'],style,element['size'])
align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(element['align']) # D/I in spanish
if isinstance(text, unicode):
text = text.encode("latin1","ignore")
else:
text = str(text)
return pdf.multi_cell(w=element['x2']-element['x1'],
h=element['y2']-element['y1'],
txt=text,align=align,split_only=True)
def render(self, outfile, dest="F"):
pdf = self.pdf
for pg in range(1, self.pg_no+1):
pdf.add_page()
pdf.set_font('Arial','B',16)
pdf.set_auto_page_break(False,margin=0)
for element in sorted(self.elements.values(),key=lambda x: x['priority']):
#print "dib",element['type'], element['name'], element['x1'], element['y1'], element['x2'], element['y2']
element = element.copy()
element['text'] = self.texts[pg].get(element['name'].lower(), element['text'])
if 'rotate' in element:
pdf.rotate(element['rotate'], element['x1'], element['y1'])
self.handlers[element['type'].upper()](pdf, **element)
if 'rotate' in element:
pdf.rotate(0)
return pdf.output(outfile, dest)
def text(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=10,
bold=False, italic=False, underline=False, align="",
foreground=0, backgroud=65535,
*args, **kwargs):
if text:
if pdf.text_color!=rgb(foreground):
pdf.set_text_color(*rgb(foreground))
if pdf.fill_color!=rgb(backgroud):
pdf.set_fill_color(*rgb(backgroud))
font = font.strip().lower()
if font == 'arial black':
font = 'arial'
style = ""
for tag in 'B', 'I', 'U':
if (text.startswith("<%s>" % tag) and text.endswith("</%s>" %tag)):
text = text[3:-4]
style += tag
if bold: style += "B"
if italic: style += "I"
if underline: style += "U"
align = {'L':'L','R':'R','I':'L','D':'R','C':'C','':''}.get(align) # D/I in spanish
pdf.set_font(font,style,size)
##m_k = 72 / 2.54
##h = (size/m_k)
pdf.set_xy(x1,y1)
pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align)
#pdf.Text(x=x1,y=y1,txt=text)
def line(self, pdf, x1=0, y1=0, x2=0, y2=0, size=0, foreground=0, *args, **kwargs):
if pdf.draw_color!=rgb(foreground):
#print "SetDrawColor", hex(foreground)
pdf.set_draw_color(*rgb(foreground))
#print "SetLineWidth", size
pdf.set_line_width(size)
pdf.line(x1, y1, x2, y2)
def rect(self, pdf, x1=0, y1=0, x2=0, y2=0, size=0, foreground=0, backgroud=65535, *args, **kwargs):
if pdf.draw_color!=rgb(foreground):
pdf.set_draw_color(*rgb(foreground))
if pdf.fill_color!=rgb(backgroud):
pdf.set_fill_color(*rgb(backgroud))
pdf.set_line_width(size)
pdf.rect(x1, y1, x2-x1, y2-y1)
def image(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', *args,**kwargs):
pdf.image(text,x1,y1,w=x2-x1,h=y2-y1,type='',link='')
def barcode(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=1,
foreground=0, *args, **kwargs):
if pdf.draw_color!=rgb(foreground):
pdf.set_draw_color(*rgb(foreground))
font = font.lower().strip()
if font == 'interleaved 2of5 nt':
pdf.interleaved2of5(text,x1,y1,w=size,h=y2-y1)
if __name__ == "__main__":
# generate sample invoice (according Argentina's regulations)
import random
from decimal import Decimal
f = Template(format="A4",
title="Sample Invoice", author="Sample Company",
subject="Sample Customer", keywords="Electronic TAX Invoice")
f.parse_csv(infile="invoice.csv", delimiter=";", decimal_sep=",")
detail = "Lorem ipsum dolor sit amet, consectetur. " * 30
items = []
for i in range(1, 30):
ds = "Sample product %s" % i
qty = random.randint(1,10)
price = round(random.random()*100,3)
code = "%s%s%02d" % (chr(random.randint(65,90)), chr(random.randint(65,90)),i)
items.append(dict(code=code, unit='u',
qty=qty, price=price,
amount=qty*price,
ds="%s: %s" % (i,ds)))
# divide and count lines
lines = 0
li_items = []
for it in items:
qty = it['qty']
code = it['code']
unit = it['unit']
for ds in f.split_multicell(it['ds'], 'item_description01'):
# add item description line (without price nor amount)
li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None))
# clean qty and code (show only at first)
unit = qty = code = None
# set last item line price and amount
li_items[-1].update(amount = it['amount'],
price = it['price'])
obs="\n<U>Detail:</U>\n\n" + detail
for ds in f.split_multicell(obs, 'item_description01'):
li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None))
# calculate pages:
lines = len(li_items)
max_lines_per_page = 24
pages = lines / (max_lines_per_page - 1)
if lines % (max_lines_per_page - 1): pages = pages + 1
# completo campos y hojas
for page in range(1, pages+1):
f.add_page()
f['page'] = 'Page %s of %s' % (page, pages)
if pages>1 and page<pages:
s = 'Continues on page %s' % (page+1)
else:
s = ''
f['item_description%02d' % (max_lines_per_page+1)] = s
f["company_name"] = "Sample Company"
f["company_logo"] = "tutorial/logo.png"
f["company_header1"] = "Some Address - somewhere -"
f["company_header2"] = "http://www.example.com"
f["company_footer1"] = "Tax Code ..."
f["company_footer2"] = "Tax/VAT ID ..."
f['number'] = '0001-00001234'
f['issue_date'] = '2010-09-10'
f['due_date'] = '2099-09-10'
f['customer_name'] = "Sample Client"
f['customer_address'] = "Siempreviva 1234"
# print line item...
li = 0
k = 0
total = Decimal("0.00")
for it in li_items:
k = k + 1
if k > page * (max_lines_per_page - 1):
break
if it['amount']:
total += Decimal("%.6f" % it['amount'])
if k > (page - 1) * (max_lines_per_page - 1):
li += 1
if it['qty'] is not None:
f['item_quantity%02d' % li] = it['qty']
if it['code'] is not None:
f['item_code%02d' % li] = it['code']
if it['unit'] is not None:
f['item_unit%02d' % li] = it['unit']
f['item_description%02d' % li] = it['ds']
if it['price'] is not None:
f['item_price%02d' % li] = "%0.3f" % it['price']
if it['amount'] is not None:
f['item_amount%02d' % li] = "%0.2f" % it['amount']
if pages == page:
f['net'] = "%0.2f" % (total/Decimal("1.21"))
f['vat'] = "%0.2f" % (total*(1-1/Decimal("1.21")))
f['total_label'] = 'Total:'
else:
f['total_label'] = 'SubTotal:'
f['total'] = "%0.2f" % total
f.render("./invoice.pdf")
if sys.platform.startswith("linux"):
os.system("evince ./invoice.pdf")
else:
os.system("./invoice.pdf")
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2010 PyMySQL contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+37
View File
@@ -0,0 +1,37 @@
====================
PyMySQL Installation
====================
.. contents::
..
This package contains a pure-Python MySQL client library.
Documentation on the MySQL client/server protocol can be found here:
http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol
If you would like to run the test suite, create a ~/.my.cnf file and
a database called "test_pymysql". The goal of pymysql is to be a drop-in
replacement for MySQLdb and work on CPython 2.3+, Jython, IronPython, PyPy
and Python 3. We test for compatibility by simply changing the import
statements in the Django MySQL backend and running its unit tests as well
as running it against the MySQLdb and myconnpy unit tests.
Requirements
-------------
+ Python 2.4 or higher
* http://www.python.org/
* 2.6 is the primary test environment.
* MySQL 4.1 or higher
* protocol41 support, experimental 4.0 support
Installation
------------
# easy_install pymysql
# ... or ...
# python setup.py install
+132
View File
@@ -0,0 +1,132 @@
'''
PyMySQL: A pure-Python drop-in replacement for MySQLdb.
Copyright (c) 2010 PyMySQL contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
'''
VERSION = (0, 4, None)
from constants import FIELD_TYPE
from converters import escape_dict, escape_sequence, escape_string
from err import Warning, Error, InterfaceError, DataError, \
DatabaseError, OperationalError, IntegrityError, InternalError, \
NotSupportedError, ProgrammingError
from times import Date, Time, Timestamp, \
DateFromTicks, TimeFromTicks, TimestampFromTicks
import sys
try:
frozenset
except NameError:
from sets import ImmutableSet as frozenset
try:
from sets import BaseSet as set
except ImportError:
from sets import Set as set
threadsafety = 1
apilevel = "2.0"
paramstyle = "format"
class DBAPISet(frozenset):
def __ne__(self, other):
if isinstance(other, set):
return super(DBAPISet, self).__ne__(self, other)
else:
return other not in self
def __eq__(self, other):
if isinstance(other, frozenset):
return frozenset.__eq__(self, other)
else:
return other in self
def __hash__(self):
return frozenset.__hash__(self)
STRING = DBAPISet([FIELD_TYPE.ENUM, FIELD_TYPE.STRING,
FIELD_TYPE.VAR_STRING])
BINARY = DBAPISet([FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB,
FIELD_TYPE.MEDIUM_BLOB, FIELD_TYPE.TINY_BLOB])
NUMBER = DBAPISet([FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT,
FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG,
FIELD_TYPE.TINY, FIELD_TYPE.YEAR])
DATE = DBAPISet([FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE])
TIME = DBAPISet([FIELD_TYPE.TIME])
TIMESTAMP = DBAPISet([FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME])
DATETIME = TIMESTAMP
ROWID = DBAPISet()
def Binary(x):
"""Return x as a binary type."""
return str(x)
def Connect(*args, **kwargs):
"""
Connect to the database; see connections.Connection.__init__() for
more information.
"""
from connections import Connection
return Connection(*args, **kwargs)
def get_client_info(): # for MySQLdb compatibility
return '%s.%s.%s' % VERSION
connect = Connection = Connect
# we include a doctored version_info here for MySQLdb compatibility
version_info = (1,2,2,"final",0)
NULL = "NULL"
__version__ = get_client_info()
def thread_safe():
return True # match MySQLdb.thread_safe()
def install_as_MySQLdb():
"""
After this function is called, any application that imports MySQLdb or
_mysql will unwittingly actually use
"""
sys.modules["MySQLdb"] = sys.modules["_mysql"] = sys.modules["pymysql"]
__all__ = [
'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', 'Date',
'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks',
'DataError', 'DatabaseError', 'Error', 'FIELD_TYPE', 'IntegrityError',
'InterfaceError', 'InternalError', 'MySQLError', 'NULL', 'NUMBER',
'NotSupportedError', 'DBAPISet', 'OperationalError', 'ProgrammingError',
'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect',
'connections', 'constants', 'converters', 'cursors', 'debug', 'escape',
'escape_dict', 'escape_sequence', 'escape_string', 'get_client_info',
'paramstyle', 'string_literal', 'threadsafety', 'version_info',
"install_as_MySQLdb",
"NULL","__version__",
]
+175
View File
@@ -0,0 +1,175 @@
MBLENGTH = {
8:1,
33:3,
88:2,
91:2
}
class Charset:
def __init__(self, id, name, collation, is_default):
self.id, self.name, self.collation = id, name, collation
self.is_default = is_default == 'Yes'
class Charsets:
def __init__(self):
self._by_id = {}
def add(self, c):
self._by_id[c.id] = c
def by_id(self, id):
return self._by_id[id]
def by_name(self, name):
for c in self._by_id.values():
if c.name == name and c.is_default:
return c
_charsets = Charsets()
"""
Generated with:
mysql -N -s -e "select id, character_set_name, collation_name, is_default
from information_schema.collations order by id;" | python -c "import sys
for l in sys.stdin.readlines():
id, name, collation, is_default = l.split(chr(9))
print '_charsets.add(Charset(%s, \'%s\', \'%s\', \'%s\'))' \
% (id, name, collation, is_default.strip())
"
"""
_charsets.add(Charset(1, 'big5', 'big5_chinese_ci', 'Yes'))
_charsets.add(Charset(2, 'latin2', 'latin2_czech_cs', ''))
_charsets.add(Charset(3, 'dec8', 'dec8_swedish_ci', 'Yes'))
_charsets.add(Charset(4, 'cp850', 'cp850_general_ci', 'Yes'))
_charsets.add(Charset(5, 'latin1', 'latin1_german1_ci', ''))
_charsets.add(Charset(6, 'hp8', 'hp8_english_ci', 'Yes'))
_charsets.add(Charset(7, 'koi8r', 'koi8r_general_ci', 'Yes'))
_charsets.add(Charset(8, 'latin1', 'latin1_swedish_ci', 'Yes'))
_charsets.add(Charset(9, 'latin2', 'latin2_general_ci', 'Yes'))
_charsets.add(Charset(10, 'swe7', 'swe7_swedish_ci', 'Yes'))
_charsets.add(Charset(11, 'ascii', 'ascii_general_ci', 'Yes'))
_charsets.add(Charset(12, 'ujis', 'ujis_japanese_ci', 'Yes'))
_charsets.add(Charset(13, 'sjis', 'sjis_japanese_ci', 'Yes'))
_charsets.add(Charset(14, 'cp1251', 'cp1251_bulgarian_ci', ''))
_charsets.add(Charset(15, 'latin1', 'latin1_danish_ci', ''))
_charsets.add(Charset(16, 'hebrew', 'hebrew_general_ci', 'Yes'))
_charsets.add(Charset(18, 'tis620', 'tis620_thai_ci', 'Yes'))
_charsets.add(Charset(19, 'euckr', 'euckr_korean_ci', 'Yes'))
_charsets.add(Charset(20, 'latin7', 'latin7_estonian_cs', ''))
_charsets.add(Charset(21, 'latin2', 'latin2_hungarian_ci', ''))
_charsets.add(Charset(22, 'koi8u', 'koi8u_general_ci', 'Yes'))
_charsets.add(Charset(23, 'cp1251', 'cp1251_ukrainian_ci', ''))
_charsets.add(Charset(24, 'gb2312', 'gb2312_chinese_ci', 'Yes'))
_charsets.add(Charset(25, 'greek', 'greek_general_ci', 'Yes'))
_charsets.add(Charset(26, 'cp1250', 'cp1250_general_ci', 'Yes'))
_charsets.add(Charset(27, 'latin2', 'latin2_croatian_ci', ''))
_charsets.add(Charset(28, 'gbk', 'gbk_chinese_ci', 'Yes'))
_charsets.add(Charset(29, 'cp1257', 'cp1257_lithuanian_ci', ''))
_charsets.add(Charset(30, 'latin5', 'latin5_turkish_ci', 'Yes'))
_charsets.add(Charset(31, 'latin1', 'latin1_german2_ci', ''))
_charsets.add(Charset(32, 'armscii8', 'armscii8_general_ci', 'Yes'))
_charsets.add(Charset(33, 'utf8', 'utf8_general_ci', 'Yes'))
_charsets.add(Charset(34, 'cp1250', 'cp1250_czech_cs', ''))
_charsets.add(Charset(35, 'ucs2', 'ucs2_general_ci', 'Yes'))
_charsets.add(Charset(36, 'cp866', 'cp866_general_ci', 'Yes'))
_charsets.add(Charset(37, 'keybcs2', 'keybcs2_general_ci', 'Yes'))
_charsets.add(Charset(38, 'macce', 'macce_general_ci', 'Yes'))
_charsets.add(Charset(39, 'macroman', 'macroman_general_ci', 'Yes'))
_charsets.add(Charset(40, 'cp852', 'cp852_general_ci', 'Yes'))
_charsets.add(Charset(41, 'latin7', 'latin7_general_ci', 'Yes'))
_charsets.add(Charset(42, 'latin7', 'latin7_general_cs', ''))
_charsets.add(Charset(43, 'macce', 'macce_bin', ''))
_charsets.add(Charset(44, 'cp1250', 'cp1250_croatian_ci', ''))
_charsets.add(Charset(47, 'latin1', 'latin1_bin', ''))
_charsets.add(Charset(48, 'latin1', 'latin1_general_ci', ''))
_charsets.add(Charset(49, 'latin1', 'latin1_general_cs', ''))
_charsets.add(Charset(50, 'cp1251', 'cp1251_bin', ''))
_charsets.add(Charset(51, 'cp1251', 'cp1251_general_ci', 'Yes'))
_charsets.add(Charset(52, 'cp1251', 'cp1251_general_cs', ''))
_charsets.add(Charset(53, 'macroman', 'macroman_bin', ''))
_charsets.add(Charset(57, 'cp1256', 'cp1256_general_ci', 'Yes'))
_charsets.add(Charset(58, 'cp1257', 'cp1257_bin', ''))
_charsets.add(Charset(59, 'cp1257', 'cp1257_general_ci', 'Yes'))
_charsets.add(Charset(63, 'binary', 'binary', 'Yes'))
_charsets.add(Charset(64, 'armscii8', 'armscii8_bin', ''))
_charsets.add(Charset(65, 'ascii', 'ascii_bin', ''))
_charsets.add(Charset(66, 'cp1250', 'cp1250_bin', ''))
_charsets.add(Charset(67, 'cp1256', 'cp1256_bin', ''))
_charsets.add(Charset(68, 'cp866', 'cp866_bin', ''))
_charsets.add(Charset(69, 'dec8', 'dec8_bin', ''))
_charsets.add(Charset(70, 'greek', 'greek_bin', ''))
_charsets.add(Charset(71, 'hebrew', 'hebrew_bin', ''))
_charsets.add(Charset(72, 'hp8', 'hp8_bin', ''))
_charsets.add(Charset(73, 'keybcs2', 'keybcs2_bin', ''))
_charsets.add(Charset(74, 'koi8r', 'koi8r_bin', ''))
_charsets.add(Charset(75, 'koi8u', 'koi8u_bin', ''))
_charsets.add(Charset(77, 'latin2', 'latin2_bin', ''))
_charsets.add(Charset(78, 'latin5', 'latin5_bin', ''))
_charsets.add(Charset(79, 'latin7', 'latin7_bin', ''))
_charsets.add(Charset(80, 'cp850', 'cp850_bin', ''))
_charsets.add(Charset(81, 'cp852', 'cp852_bin', ''))
_charsets.add(Charset(82, 'swe7', 'swe7_bin', ''))
_charsets.add(Charset(83, 'utf8', 'utf8_bin', ''))
_charsets.add(Charset(84, 'big5', 'big5_bin', ''))
_charsets.add(Charset(85, 'euckr', 'euckr_bin', ''))
_charsets.add(Charset(86, 'gb2312', 'gb2312_bin', ''))
_charsets.add(Charset(87, 'gbk', 'gbk_bin', ''))
_charsets.add(Charset(88, 'sjis', 'sjis_bin', ''))
_charsets.add(Charset(89, 'tis620', 'tis620_bin', ''))
_charsets.add(Charset(90, 'ucs2', 'ucs2_bin', ''))
_charsets.add(Charset(91, 'ujis', 'ujis_bin', ''))
_charsets.add(Charset(92, 'geostd8', 'geostd8_general_ci', 'Yes'))
_charsets.add(Charset(93, 'geostd8', 'geostd8_bin', ''))
_charsets.add(Charset(94, 'latin1', 'latin1_spanish_ci', ''))
_charsets.add(Charset(95, 'cp932', 'cp932_japanese_ci', 'Yes'))
_charsets.add(Charset(96, 'cp932', 'cp932_bin', ''))
_charsets.add(Charset(97, 'eucjpms', 'eucjpms_japanese_ci', 'Yes'))
_charsets.add(Charset(98, 'eucjpms', 'eucjpms_bin', ''))
_charsets.add(Charset(99, 'cp1250', 'cp1250_polish_ci', ''))
_charsets.add(Charset(128, 'ucs2', 'ucs2_unicode_ci', ''))
_charsets.add(Charset(129, 'ucs2', 'ucs2_icelandic_ci', ''))
_charsets.add(Charset(130, 'ucs2', 'ucs2_latvian_ci', ''))
_charsets.add(Charset(131, 'ucs2', 'ucs2_romanian_ci', ''))
_charsets.add(Charset(132, 'ucs2', 'ucs2_slovenian_ci', ''))
_charsets.add(Charset(133, 'ucs2', 'ucs2_polish_ci', ''))
_charsets.add(Charset(134, 'ucs2', 'ucs2_estonian_ci', ''))
_charsets.add(Charset(135, 'ucs2', 'ucs2_spanish_ci', ''))
_charsets.add(Charset(136, 'ucs2', 'ucs2_swedish_ci', ''))
_charsets.add(Charset(137, 'ucs2', 'ucs2_turkish_ci', ''))
_charsets.add(Charset(138, 'ucs2', 'ucs2_czech_ci', ''))
_charsets.add(Charset(139, 'ucs2', 'ucs2_danish_ci', ''))
_charsets.add(Charset(140, 'ucs2', 'ucs2_lithuanian_ci', ''))
_charsets.add(Charset(141, 'ucs2', 'ucs2_slovak_ci', ''))
_charsets.add(Charset(142, 'ucs2', 'ucs2_spanish2_ci', ''))
_charsets.add(Charset(143, 'ucs2', 'ucs2_roman_ci', ''))
_charsets.add(Charset(144, 'ucs2', 'ucs2_persian_ci', ''))
_charsets.add(Charset(145, 'ucs2', 'ucs2_esperanto_ci', ''))
_charsets.add(Charset(146, 'ucs2', 'ucs2_hungarian_ci', ''))
_charsets.add(Charset(192, 'utf8', 'utf8_unicode_ci', ''))
_charsets.add(Charset(193, 'utf8', 'utf8_icelandic_ci', ''))
_charsets.add(Charset(194, 'utf8', 'utf8_latvian_ci', ''))
_charsets.add(Charset(195, 'utf8', 'utf8_romanian_ci', ''))
_charsets.add(Charset(196, 'utf8', 'utf8_slovenian_ci', ''))
_charsets.add(Charset(197, 'utf8', 'utf8_polish_ci', ''))
_charsets.add(Charset(198, 'utf8', 'utf8_estonian_ci', ''))
_charsets.add(Charset(199, 'utf8', 'utf8_spanish_ci', ''))
_charsets.add(Charset(200, 'utf8', 'utf8_swedish_ci', ''))
_charsets.add(Charset(201, 'utf8', 'utf8_turkish_ci', ''))
_charsets.add(Charset(202, 'utf8', 'utf8_czech_ci', ''))
_charsets.add(Charset(203, 'utf8', 'utf8_danish_ci', ''))
_charsets.add(Charset(204, 'utf8', 'utf8_lithuanian_ci', ''))
_charsets.add(Charset(205, 'utf8', 'utf8_slovak_ci', ''))
_charsets.add(Charset(206, 'utf8', 'utf8_spanish2_ci', ''))
_charsets.add(Charset(207, 'utf8', 'utf8_roman_ci', ''))
_charsets.add(Charset(208, 'utf8', 'utf8_persian_ci', ''))
_charsets.add(Charset(209, 'utf8', 'utf8_esperanto_ci', ''))
_charsets.add(Charset(210, 'utf8', 'utf8_hungarian_ci', ''))
def charset_by_name(name):
return _charsets.by_name(name)
def charset_by_id(id):
return _charsets.by_id(id)
+933
View File
@@ -0,0 +1,933 @@
# Python implementation of the MySQL client-server protocol
# http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol
try:
import hashlib
sha_new = lambda *args, **kwargs: hashlib.new("sha1", *args, **kwargs)
except ImportError:
import sha
sha_new = sha.new
import socket
try:
import ssl
SSL_ENABLED = True
except ImportError:
SSL_ENABLED = False
import struct
import sys
import os
import ConfigParser
try:
import cStringIO as StringIO
except ImportError:
import StringIO
from charset import MBLENGTH, charset_by_name, charset_by_id
from cursors import Cursor
from constants import FIELD_TYPE, FLAG
from constants import SERVER_STATUS
from constants.CLIENT import *
from constants.COMMAND import *
from util import join_bytes, byte2int, int2byte
from converters import escape_item, encoders, decoders
from err import raise_mysql_exception, Warning, Error, \
InterfaceError, DataError, DatabaseError, OperationalError, \
IntegrityError, InternalError, NotSupportedError, ProgrammingError
DEBUG = False
NULL_COLUMN = 251
UNSIGNED_CHAR_COLUMN = 251
UNSIGNED_SHORT_COLUMN = 252
UNSIGNED_INT24_COLUMN = 253
UNSIGNED_INT64_COLUMN = 254
UNSIGNED_CHAR_LENGTH = 1
UNSIGNED_SHORT_LENGTH = 2
UNSIGNED_INT24_LENGTH = 3
UNSIGNED_INT64_LENGTH = 8
DEFAULT_CHARSET = 'latin1'
MAX_PACKET_LENGTH = 256*256*256-1
def dump_packet(data):
def is_ascii(data):
if byte2int(data) >= 65 and byte2int(data) <= 122: #data.isalnum():
return data
return '.'
print "packet length %d" % len(data)
print "method call[1]: %s" % sys._getframe(1).f_code.co_name
print "method call[2]: %s" % sys._getframe(2).f_code.co_name
print "method call[3]: %s" % sys._getframe(3).f_code.co_name
print "method call[4]: %s" % sys._getframe(4).f_code.co_name
print "method call[5]: %s" % sys._getframe(5).f_code.co_name
print "-" * 88
dump_data = [data[i:i+16] for i in xrange(len(data)) if i%16 == 0]
for d in dump_data:
print ' '.join(map(lambda x:"%02X" % byte2int(x), d)) + \
' ' * (16 - len(d)) + ' ' * 2 + \
' '.join(map(lambda x:"%s" % is_ascii(x), d))
print "-" * 88
print ""
def _scramble(password, message):
if password == None or len(password) == 0:
return int2byte(0)
if DEBUG: print 'password=' + password
stage1 = sha_new(password).digest()
stage2 = sha_new(stage1).digest()
s = sha_new()
s.update(message)
s.update(stage2)
result = s.digest()
return _my_crypt(result, stage1)
def _my_crypt(message1, message2):
length = len(message1)
result = struct.pack('B', length)
for i in xrange(length):
x = (struct.unpack('B', message1[i:i+1])[0] ^ \
struct.unpack('B', message2[i:i+1])[0])
result += struct.pack('B', x)
return result
# old_passwords support ported from libmysql/password.c
SCRAMBLE_LENGTH_323 = 8
class RandStruct_323(object):
def __init__(self, seed1, seed2):
self.max_value = 0x3FFFFFFFL
self.seed1 = seed1 % self.max_value
self.seed2 = seed2 % self.max_value
def my_rnd(self):
self.seed1 = (self.seed1 * 3L + self.seed2) % self.max_value
self.seed2 = (self.seed1 + self.seed2 + 33L) % self.max_value
return float(self.seed1) / float(self.max_value)
def _scramble_323(password, message):
hash_pass = _hash_password_323(password)
hash_message = _hash_password_323(message[:SCRAMBLE_LENGTH_323])
hash_pass_n = struct.unpack(">LL", hash_pass)
hash_message_n = struct.unpack(">LL", hash_message)
rand_st = RandStruct_323(hash_pass_n[0] ^ hash_message_n[0],
hash_pass_n[1] ^ hash_message_n[1])
outbuf = StringIO.StringIO()
for _ in xrange(min(SCRAMBLE_LENGTH_323, len(message))):
outbuf.write(int2byte(int(rand_st.my_rnd() * 31) + 64))
extra = int2byte(int(rand_st.my_rnd() * 31))
out = outbuf.getvalue()
outbuf = StringIO.StringIO()
for c in out:
outbuf.write(int2byte(byte2int(c) ^ byte2int(extra)))
return outbuf.getvalue()
def _hash_password_323(password):
nr = 1345345333L
add = 7L
nr2 = 0x12345671L
for c in [byte2int(x) for x in password if x not in (' ', '\t')]:
nr^= (((nr & 63)+add)*c)+ (nr << 8) & 0xFFFFFFFF
nr2= (nr2 + ((nr2 << 8) ^ nr)) & 0xFFFFFFFF
add= (add + c) & 0xFFFFFFFF
r1 = nr & ((1L << 31) - 1L) # kill sign bits
r2 = nr2 & ((1L << 31) - 1L)
# pack
return struct.pack(">LL", r1, r2)
def pack_int24(n):
return struct.pack('BBB', n&0xFF, (n>>8)&0xFF, (n>>16)&0xFF)
def unpack_uint16(n):
return struct.unpack('<H', n[0:2])[0]
# TODO: stop using bit-shifting in these functions...
# TODO: rename to "uint" to make it clear they're unsigned...
def unpack_int24(n):
return struct.unpack('B',n[0])[0] + (struct.unpack('B', n[1])[0] << 8) +\
(struct.unpack('B',n[2])[0] << 16)
def unpack_int32(n):
return struct.unpack('B',n[0])[0] + (struct.unpack('B', n[1])[0] << 8) +\
(struct.unpack('B',n[2])[0] << 16) + (struct.unpack('B', n[3])[0] << 24)
def unpack_int64(n):
return struct.unpack('B',n[0])[0] + (struct.unpack('B', n[1])[0]<<8) +\
(struct.unpack('B',n[2])[0] << 16) + (struct.unpack('B',n[3])[0]<<24)+\
(struct.unpack('B',n[4])[0] << 32) + (struct.unpack('B',n[5])[0]<<40)+\
(struct.unpack('B',n[6])[0] << 48) + (struct.unpack('B',n[7])[0]<<56)
def defaulterrorhandler(connection, cursor, errorclass, errorvalue):
err = errorclass, errorvalue
if DEBUG:
raise
if cursor:
cursor.messages.append(err)
else:
connection.messages.append(err)
del cursor
del connection
if not issubclass(errorclass, Error):
raise Error(errorclass, errorvalue)
else:
raise errorclass, errorvalue
class MysqlPacket(object):
"""Representation of a MySQL response packet. Reads in the packet
from the network socket, removes packet header and provides an interface
for reading/parsing the packet results."""
def __init__(self, socket):
self.__position = 0
self.__recv_packet(socket)
del socket
def __recv_packet(self, socket):
"""Parse the packet header and read entire packet payload into buffer."""
packet_header = socket.recv(4)
while len(packet_header) < 4:
d = socket.recv(4 - len(packet_header))
if len(d) == 0:
raise OperationalError(2013, "Lost connection to MySQL server during query")
packet_header += d
if DEBUG: dump_packet(packet_header)
packet_length_bin = packet_header[:3]
self.__packet_number = byte2int(packet_header[3])
# TODO: check packet_num is correct (+1 from last packet)
bin_length = packet_length_bin + int2byte(0) # pad little-endian number
bytes_to_read = struct.unpack('<I', bin_length)[0]
payload_buff = [] # this is faster than cStringIO
while bytes_to_read > 0:
recv_data = socket.recv(bytes_to_read)
if len(recv_data) == 0:
raise OperationalError(2013, "Lost connection to MySQL server during query")
if DEBUG: dump_packet(recv_data)
payload_buff.append(recv_data)
bytes_to_read -= len(recv_data)
self.__data = join_bytes(payload_buff)
def packet_number(self): return self.__packet_number
def get_all_data(self): return self.__data
def read(self, size):
"""Read the first 'size' bytes in packet and advance cursor past them."""
result = self.peek(size)
self.advance(size)
return result
def read_all(self):
"""Read all remaining data in the packet.
(Subsequent read() or peek() will return errors.)
"""
result = self.__data[self.__position:]
self.__position = None # ensure no subsequent read() or peek()
return result
def advance(self, length):
"""Advance the cursor in data buffer 'length' bytes."""
new_position = self.__position + length
if new_position < 0 or new_position > len(self.__data):
raise Exception('Invalid advance amount (%s) for cursor. '
'Position=%s' % (length, new_position))
self.__position = new_position
def rewind(self, position=0):
"""Set the position of the data buffer cursor to 'position'."""
if position < 0 or position > len(self.__data):
raise Exception("Invalid position to rewind cursor to: %s." % position)
self.__position = position
def peek(self, size):
"""Look at the first 'size' bytes in packet without moving cursor."""
result = self.__data[self.__position:(self.__position+size)]
if len(result) != size:
error = ('Result length not requested length:\n'
'Expected=%s. Actual=%s. Position: %s. Data Length: %s'
% (size, len(result), self.__position, len(self.__data)))
if DEBUG:
print error
self.dump()
raise AssertionError(error)
return result
def get_bytes(self, position, length=1):
"""Get 'length' bytes starting at 'position'.
Position is start of payload (first four packet header bytes are not
included) starting at index '0'.
No error checking is done. If requesting outside end of buffer
an empty string (or string shorter than 'length') may be returned!
"""
return self.__data[position:(position+length)]
def read_length_coded_binary(self):
"""Read a 'Length Coded Binary' number from the data buffer.
Length coded numbers can be anywhere from 1 to 9 bytes depending
on the value of the first byte.
"""
c = byte2int(self.read(1))
if c == NULL_COLUMN:
return None
if c < UNSIGNED_CHAR_COLUMN:
return c
elif c == UNSIGNED_SHORT_COLUMN:
return unpack_uint16(self.read(UNSIGNED_SHORT_LENGTH))
elif c == UNSIGNED_INT24_COLUMN:
return unpack_int24(self.read(UNSIGNED_INT24_LENGTH))
elif c == UNSIGNED_INT64_COLUMN:
# TODO: what was 'longlong'? confirm it wasn't used?
return unpack_int64(self.read(UNSIGNED_INT64_LENGTH))
def read_length_coded_string(self):
"""Read a 'Length Coded String' from the data buffer.
A 'Length Coded String' consists first of a length coded
(unsigned, positive) integer represented in 1-9 bytes followed by
that many bytes of binary data. (For example "cat" would be "3cat".)
"""
length = self.read_length_coded_binary()
if length is None:
return None
return self.read(length)
def is_ok_packet(self):
return byte2int(self.get_bytes(0)) == 0
def is_eof_packet(self):
return byte2int(self.get_bytes(0)) == 254 # 'fe'
def is_resultset_packet(self):
field_count = byte2int(self.get_bytes(0))
return field_count >= 1 and field_count <= 250
def is_error_packet(self):
return byte2int(self.get_bytes(0)) == 255
def check_error(self):
if self.is_error_packet():
self.rewind()
self.advance(1) # field_count == error (we already know that)
errno = unpack_uint16(self.read(2))
if DEBUG: print "errno = %d" % errno
raise_mysql_exception(self.__data)
def dump(self):
dump_packet(self.__data)
class FieldDescriptorPacket(MysqlPacket):
"""A MysqlPacket that represents a specific column's metadata in the result.
Parsing is automatically done and the results are exported via public
attributes on the class such as: db, table_name, name, length, type_code.
"""
def __init__(self, *args):
MysqlPacket.__init__(self, *args)
self.__parse_field_descriptor()
def __parse_field_descriptor(self):
"""Parse the 'Field Descriptor' (Metadata) packet.
This is compatible with MySQL 4.1+ (not compatible with MySQL 4.0).
"""
self.catalog = self.read_length_coded_string()
self.db = self.read_length_coded_string()
self.table_name = self.read_length_coded_string()
self.org_table = self.read_length_coded_string()
self.name = self.read_length_coded_string()
self.org_name = self.read_length_coded_string()
self.advance(1) # non-null filler
self.charsetnr = struct.unpack('<H', self.read(2))[0]
self.length = struct.unpack('<I', self.read(4))[0]
self.type_code = byte2int(self.read(1))
self.flags = struct.unpack('<H', self.read(2))[0]
self.scale = byte2int(self.read(1)) # "decimals"
self.advance(2) # filler (always 0x00)
# 'default' is a length coded binary and is still in the buffer?
# not used for normal result sets...
def description(self):
"""Provides a 7-item tuple compatible with the Python PEP249 DB Spec."""
desc = []
desc.append(self.name)
desc.append(self.type_code)
desc.append(None) # TODO: display_length; should this be self.length?
desc.append(self.get_column_length()) # 'internal_size'
desc.append(self.get_column_length()) # 'precision' # TODO: why!?!?
desc.append(self.scale)
# 'null_ok' -- can this be True/False rather than 1/0?
# if so just do: desc.append(bool(self.flags % 2 == 0))
if self.flags % 2 == 0:
desc.append(1)
else:
desc.append(0)
return tuple(desc)
def get_column_length(self):
if self.type_code == FIELD_TYPE.VAR_STRING:
mblen = MBLENGTH.get(self.charsetnr, 1)
return self.length // mblen
return self.length
def __str__(self):
return ('%s %s.%s.%s, type=%s'
% (self.__class__, self.db, self.table_name, self.name,
self.type_code))
class Connection(object):
"""
Representation of a socket with a mysql server.
The proper way to get an instance of this class is to call
connect()."""
errorhandler = defaulterrorhandler
def __init__(self, host="localhost", user=None, passwd="",
db=None, port=3306, unix_socket=None,
charset='', sql_mode=None,
read_default_file=None, conv=decoders, use_unicode=None,
client_flag=0, cursorclass=Cursor, init_command=None,
connect_timeout=None, ssl=None, read_default_group=None,
compress=None, named_pipe=None):
"""
Establish a connection to the MySQL database. Accepts several
arguments:
host: Host where the database server is located
user: Username to log in as
passwd: Password to use.
db: Database to use, None to not use a particular one.
port: MySQL port to use, default is usually OK.
unix_socket: Optionally, you can use a unix socket rather than TCP/IP.
charset: Charset you want to use.
sql_mode: Default SQL_MODE to use.
read_default_file: Specifies my.cnf file to read these parameters from under the [client] section.
conv: Decoders dictionary to use instead of the default one. This is used to provide custom marshalling of types. See converters.
use_unicode: Whether or not to default to unicode strings. This option defaults to true for Py3k.
client_flag: Custom flags to send to MySQL. Find potential values in constants.CLIENT.
cursorclass: Custom cursor class to use.
init_command: Initial SQL statement to run when connection is established.
connect_timeout: Timeout before throwing an exception when connecting.
ssl: A dict of arguments similar to mysql_ssl_set()'s parameters. For now the capath and cipher arguments are not supported.
read_default_group: Group to read from in the configuration file.
compress; Not supported
named_pipe: Not supported
"""
if use_unicode is None and sys.version_info[0] > 2:
use_unicode = True
if compress or named_pipe:
raise NotImplementedError, "compress and named_pipe arguments are not supported"
if ssl and (ssl.has_key('capath') or ssl.has_key('cipher')):
raise NotImplementedError, 'ssl options capath and cipher are not supported'
self.ssl = False
if ssl:
if not SSL_ENABLED:
raise NotImplementedError, "ssl module not found"
self.ssl = True
client_flag |= SSL
for k in ('key', 'cert', 'ca'):
v = None
if ssl.has_key(k):
v = ssl[k]
setattr(self, k, v)
if read_default_group and not read_default_file:
if sys.platform.startswith("win"):
read_default_file = "c:\\my.ini"
else:
read_default_file = "/etc/my.cnf"
if read_default_file:
if not read_default_group:
read_default_group = "client"
cfg = ConfigParser.RawConfigParser()
cfg.read(os.path.expanduser(read_default_file))
def _config(key, default):
try:
return cfg.get(read_default_group,key)
except:
return default
user = _config("user",user)
passwd = _config("password",passwd)
host = _config("host", host)
db = _config("db",db)
unix_socket = _config("socket",unix_socket)
port = _config("port", port)
charset = _config("default-character-set", charset)
self.host = host
self.port = port
self.user = user
self.password = passwd
self.db = db
self.unix_socket = unix_socket
if charset:
self.charset = charset
self.use_unicode = True
else:
self.charset = DEFAULT_CHARSET
self.use_unicode = False
if use_unicode:
self.use_unicode = use_unicode
client_flag |= CAPABILITIES
client_flag |= MULTI_STATEMENTS
if self.db:
client_flag |= CONNECT_WITH_DB
self.client_flag = client_flag
self.cursorclass = cursorclass
self.connect_timeout = connect_timeout
self._connect()
self.messages = []
self.set_charset(charset)
self.encoders = encoders
self.decoders = conv
self._affected_rows = 0
self.host_info = "Not connected"
self.autocommit(False)
if sql_mode is not None:
c = self.cursor()
c.execute("SET sql_mode=%s", (sql_mode,))
self.commit()
if init_command is not None:
c = self.cursor()
c.execute(init_command)
self.commit()
def close(self):
''' Send the quit message and close the socket '''
send_data = struct.pack('<i',1) + int2byte(COM_QUIT)
self.socket.send(send_data)
self.socket.close()
self.socket = None
def autocommit(self, value):
''' Set whether or not to commit after every execute() '''
try:
self._execute_command(COM_QUERY, "SET AUTOCOMMIT = %s" % \
self.escape(value))
self.read_packet()
except:
exc,value,tb = sys.exc_info()
self.errorhandler(None, exc, value)
def commit(self):
''' Commit changes to stable storage '''
try:
self._execute_command(COM_QUERY, "COMMIT")
self.read_packet()
except:
exc,value,tb = sys.exc_info()
self.errorhandler(None, exc, value)
def rollback(self):
''' Roll back the current transaction '''
try:
self._execute_command(COM_QUERY, "ROLLBACK")
self.read_packet()
except:
exc,value,tb = sys.exc_info()
self.errorhandler(None, exc, value)
def escape(self, obj):
''' Escape whatever value you pass to it '''
return escape_item(obj, self.charset)
def literal(self, obj):
''' Alias for escape() '''
return escape_item(obj, self.charset)
def cursor(self):
''' Create a new cursor to execute queries with '''
return self.cursorclass(self)
def __enter__(self):
''' Context manager that returns a Cursor '''
return self.cursor()
def __exit__(self, exc, value, traceback):
''' On successful exit, commit. On exception, rollback. '''
if exc:
self.rollback()
else:
self.commit()
# The following methods are INTERNAL USE ONLY (called from Cursor)
def query(self, sql):
self._execute_command(COM_QUERY, sql)
self._affected_rows = self._read_query_result()
return self._affected_rows
def next_result(self):
self._affected_rows = self._read_query_result()
return self._affected_rows
def affected_rows(self):
return self._affected_rows
def kill(self, thread_id):
arg = struct.pack('<I', thread_id)
try:
self._execute_command(COM_PROCESS_KILL, arg)
except:
exc,value,tb = sys.exc_info()
self.errorhandler(None, exc, value)
return
pkt = self.read_packet()
return pkt.is_ok_packet()
def ping(self, reconnect=True):
''' Check if the server is alive '''
try:
self._execute_command(COM_PING, "")
except:
if reconnect:
self._connect()
return self.ping(False)
else:
exc,value,tb = sys.exc_info()
self.errorhandler(None, exc, value)
return
pkt = self.read_packet()
return pkt.is_ok_packet()
def set_charset(self, charset):
try:
if charset:
self._execute_command(COM_QUERY, "SET NAMES %s" %
self.escape(charset))
self.read_packet()
self.charset = charset
except:
exc,value,tb = sys.exc_info()
self.errorhandler(None, exc, value)
def _connect(self):
try:
if self.unix_socket and (self.host == 'localhost' or self.host == '127.0.0.1'):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
t = sock.gettimeout()
sock.settimeout(self.connect_timeout)
sock.connect(self.unix_socket)
sock.settimeout(t)
self.host_info = "Localhost via UNIX socket"
if DEBUG: print 'connected using unix_socket'
else:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
t = sock.gettimeout()
sock.settimeout(self.connect_timeout)
sock.connect((self.host, self.port))
sock.settimeout(t)
self.host_info = "socket %s:%d" % (self.host, self.port)
if DEBUG: print 'connected using socket'
self.socket = sock
self._get_server_information()
self._request_authentication()
except socket.error, e:
raise OperationalError(2003, "Can't connect to MySQL server on %r (%d)" % (self.host, e.args[0]))
def read_packet(self, packet_type=MysqlPacket):
"""Read an entire "mysql packet" in its entirety from the network
and return a MysqlPacket type that represents the results."""
# TODO: is socket.recv(small_number) significantly slower than
# socket.recv(large_number)? if so, maybe we should buffer
# the socket.recv() (though that obviously makes memory management
# more complicated.
packet = packet_type(self.socket)
packet.check_error()
return packet
def _read_query_result(self):
result = MySQLResult(self)
result.read()
self._result = result
return result.affected_rows
def _send_command(self, command, sql):
#send_data = struct.pack('<i', len(sql) + 1) + command + sql
# could probably be more efficient, at least it's correct
if not self.socket:
self.errorhandler(None, InterfaceError, "(0, '')")
if isinstance(sql, unicode):
sql = sql.encode(self.charset)
buf = int2byte(command) + sql
pckt_no = 0
while len(buf) >= MAX_PACKET_LENGTH:
header = struct.pack('<i', MAX_PACKET_LENGTH)[:-1]+int2byte(pckt_no)
send_data = header + buf[:MAX_PACKET_LENGTH]
self.socket.send(send_data)
if DEBUG: dump_packet(send_data)
buf = buf[MAX_PACKET_LENGTH:]
pckt_no += 1
header = struct.pack('<i', len(buf))[:-1]+int2byte(pckt_no)
self.socket.send(header+buf)
#sock = self.socket
#sock.send(send_data)
#
def _execute_command(self, command, sql):
self._send_command(command, sql)
def _request_authentication(self):
self._send_authentication()
def _send_authentication(self):
sock = self.socket
self.client_flag |= CAPABILITIES
if self.server_version.startswith('5'):
self.client_flag |= MULTI_RESULTS
if self.user is None:
raise ValueError, "Did not specify a username"
charset_id = charset_by_name(self.charset).id
self.user = self.user.encode(self.charset)
data_init = struct.pack('<i', self.client_flag) + struct.pack("<I", 1) + \
int2byte(charset_id) + int2byte(0)*23
next_packet = 1
if self.ssl:
data = pack_int24(len(data_init)) + int2byte(next_packet) + data_init
next_packet += 1
if DEBUG: dump_packet(data)
sock.send(data)
sock = self.socket = ssl.wrap_socket(sock, keyfile=self.key,
certfile=self.cert,
ssl_version=ssl.PROTOCOL_TLSv1,
cert_reqs=ssl.CERT_REQUIRED,
ca_certs=self.ca)
data = data_init + self.user+int2byte(0) + _scramble(self.password.encode(self.charset), self.salt)
if self.db:
self.db = self.db.encode(self.charset)
data += self.db + int2byte(0)
data = pack_int24(len(data)) + int2byte(next_packet) + data
next_packet += 2
if DEBUG: dump_packet(data)
sock.send(data)
auth_packet = MysqlPacket(sock)
auth_packet.check_error()
if DEBUG: auth_packet.dump()
# if old_passwords is enabled the packet will be 1 byte long and
# have the octet 254
if auth_packet.is_eof_packet():
# send legacy handshake
#raise NotImplementedError, "old_passwords are not supported. Check to see if mysqld was started with --old-passwords, if old-passwords=1 in a my.cnf file, or if there are some short hashes in your mysql.user table."
# TODO: is this the correct charset?
data = _scramble_323(self.password.encode(self.charset), self.salt.encode(self.charset)) + int2byte(0)
data = pack_int24(len(data)) + int2byte(next_packet) + data
sock.send(data)
auth_packet = MysqlPacket(sock)
auth_packet.check_error()
if DEBUG: auth_packet.dump()
# _mysql support
def thread_id(self):
return self.server_thread_id[0]
def character_set_name(self):
return self.charset
def get_host_info(self):
return self.host_info
def get_proto_info(self):
return self.protocol_version
def _get_server_information(self):
sock = self.socket
i = 0
packet = MysqlPacket(sock)
data = packet.get_all_data()
if DEBUG: dump_packet(data)
#packet_len = byte2int(data[i:i+1])
#i += 4
self.protocol_version = byte2int(data[i:i+1])
i += 1
server_end = data.find(int2byte(0), i)
# TODO: is this the correct charset? should it be default_charset?
self.server_version = data[i:server_end].decode(self.charset)
i = server_end + 1
self.server_thread_id = struct.unpack('<h', data[i:i+2])
i += 4
self.salt = data[i:i+8]
i += 9
if len(data) >= i + 1:
i += 1
self.server_capabilities = struct.unpack('<h', data[i:i+2])[0]
i += 1
self.server_language = byte2int(data[i:i+1])
self.server_charset = charset_by_id(self.server_language).name
i += 16
if len(data) >= i+12-1:
rest_salt = data[i:i+12]
self.salt += rest_salt
def get_server_info(self):
return self.server_version
Warning = Warning
Error = Error
InterfaceError = InterfaceError
DatabaseError = DatabaseError
DataError = DataError
OperationalError = OperationalError
IntegrityError = IntegrityError
InternalError = InternalError
ProgrammingError = ProgrammingError
NotSupportedError = NotSupportedError
# TODO: move OK and EOF packet parsing/logic into a proper subclass
# of MysqlPacket like has been done with FieldDescriptorPacket.
class MySQLResult(object):
def __init__(self, connection):
from weakref import proxy
self.connection = proxy(connection)
self.affected_rows = None
self.insert_id = None
self.server_status = 0
self.warning_count = 0
self.message = None
self.field_count = 0
self.description = None
self.rows = None
self.has_next = None
def read(self):
self.first_packet = self.connection.read_packet()
# TODO: use classes for different packet types?
if self.first_packet.is_ok_packet():
self._read_ok_packet()
else:
self._read_result_packet()
def _read_ok_packet(self):
self.first_packet.advance(1) # field_count (always '0')
self.affected_rows = self.first_packet.read_length_coded_binary()
self.insert_id = self.first_packet.read_length_coded_binary()
self.server_status = struct.unpack('<H', self.first_packet.read(2))[0]
self.warning_count = struct.unpack('<H', self.first_packet.read(2))[0]
self.message = self.first_packet.read_all()
def _read_result_packet(self):
self.field_count = byte2int(self.first_packet.read(1))
self._get_descriptions()
self._read_rowdata_packet()
# TODO: implement this as an iteratable so that it is more
# memory efficient and lower-latency to client...
def _read_rowdata_packet(self):
"""Read a rowdata packet for each data row in the result set."""
rows = []
while True:
packet = self.connection.read_packet()
if packet.is_eof_packet():
self.warning_count = packet.read(2)
server_status = struct.unpack('<h', packet.read(2))[0]
self.has_next = (server_status
& SERVER_STATUS.SERVER_MORE_RESULTS_EXISTS)
break
row = []
for field in self.fields:
if field.type_code in self.connection.decoders:
converter = self.connection.decoders[field.type_code]
if DEBUG: print "DEBUG: field=%s, converter=%s" % (field, converter)
data = packet.read_length_coded_string()
converted = None
if data != None:
converted = converter(self.connection, field, data)
row.append(converted)
rows.append(tuple(row))
self.affected_rows = len(rows)
self.rows = tuple(rows)
if DEBUG: self.rows
def _get_descriptions(self):
"""Read a column descriptor packet for each column in the result."""
self.fields = []
description = []
for i in xrange(self.field_count):
field = self.connection.read_packet(FieldDescriptorPacket)
self.fields.append(field)
description.append(field.description())
eof_packet = self.connection.read_packet()
assert eof_packet.is_eof_packet(), 'Protocol error, expecting EOF'
self.description = tuple(description)
+20
View File
@@ -0,0 +1,20 @@
LONG_PASSWORD = 1
FOUND_ROWS = 1 << 1
LONG_FLAG = 1 << 2
CONNECT_WITH_DB = 1 << 3
NO_SCHEMA = 1 << 4
COMPRESS = 1 << 5
ODBC = 1 << 6
LOCAL_FILES = 1 << 7
IGNORE_SPACE = 1 << 8
PROTOCOL_41 = 1 << 9
INTERACTIVE = 1 << 10
SSL = 1 << 11
IGNORE_SIGPIPE = 1 << 12
TRANSACTIONS = 1 << 13
SECURE_CONNECTION = 1 << 15
MULTI_STATEMENTS = 1 << 16
MULTI_RESULTS = 1 << 17
CAPABILITIES = LONG_PASSWORD|LONG_FLAG|TRANSACTIONS| \
PROTOCOL_41|SECURE_CONNECTION
@@ -0,0 +1,23 @@
COM_SLEEP = 0x00
COM_QUIT = 0x01
COM_INIT_DB = 0x02
COM_QUERY = 0x03
COM_FIELD_LIST = 0x04
COM_CREATE_DB = 0x05
COM_DROP_DB = 0x06
COM_REFRESH = 0x07
COM_SHUTDOWN = 0x08
COM_STATISTICS = 0x09
COM_PROCESS_INFO = 0x0a
COM_CONNECT = 0x0b
COM_PROCESS_KILL = 0x0c
COM_DEBUG = 0x0d
COM_PING = 0x0e
COM_TIME = 0x0f
COM_DELAYED_INSERT = 0x10
COM_CHANGE_USER = 0x11
COM_BINLOG_DUMP = 0x12
COM_TABLE_DUMP = 0x13
COM_CONNECT_OUT = 0x14
COM_REGISTER_SLAVE = 0x15
+472
View File
@@ -0,0 +1,472 @@
ERROR_FIRST = 1000
HASHCHK = 1000
NISAMCHK = 1001
NO = 1002
YES = 1003
CANT_CREATE_FILE = 1004
CANT_CREATE_TABLE = 1005
CANT_CREATE_DB = 1006
DB_CREATE_EXISTS = 1007
DB_DROP_EXISTS = 1008
DB_DROP_DELETE = 1009
DB_DROP_RMDIR = 1010
CANT_DELETE_FILE = 1011
CANT_FIND_SYSTEM_REC = 1012
CANT_GET_STAT = 1013
CANT_GET_WD = 1014
CANT_LOCK = 1015
CANT_OPEN_FILE = 1016
FILE_NOT_FOUND = 1017
CANT_READ_DIR = 1018
CANT_SET_WD = 1019
CHECKREAD = 1020
DISK_FULL = 1021
DUP_KEY = 1022
ERROR_ON_CLOSE = 1023
ERROR_ON_READ = 1024
ERROR_ON_RENAME = 1025
ERROR_ON_WRITE = 1026
FILE_USED = 1027
FILSORT_ABORT = 1028
FORM_NOT_FOUND = 1029
GET_ERRNO = 1030
ILLEGAL_HA = 1031
KEY_NOT_FOUND = 1032
NOT_FORM_FILE = 1033
NOT_KEYFILE = 1034
OLD_KEYFILE = 1035
OPEN_AS_READONLY = 1036
OUTOFMEMORY = 1037
OUT_OF_SORTMEMORY = 1038
UNEXPECTED_EOF = 1039
CON_COUNT_ERROR = 1040
OUT_OF_RESOURCES = 1041
BAD_HOST_ERROR = 1042
HANDSHAKE_ERROR = 1043
DBACCESS_DENIED_ERROR = 1044
ACCESS_DENIED_ERROR = 1045
NO_DB_ERROR = 1046
UNKNOWN_COM_ERROR = 1047
BAD_NULL_ERROR = 1048
BAD_DB_ERROR = 1049
TABLE_EXISTS_ERROR = 1050
BAD_TABLE_ERROR = 1051
NON_UNIQ_ERROR = 1052
SERVER_SHUTDOWN = 1053
BAD_FIELD_ERROR = 1054
WRONG_FIELD_WITH_GROUP = 1055
WRONG_GROUP_FIELD = 1056
WRONG_SUM_SELECT = 1057
WRONG_VALUE_COUNT = 1058
TOO_LONG_IDENT = 1059
DUP_FIELDNAME = 1060
DUP_KEYNAME = 1061
DUP_ENTRY = 1062
WRONG_FIELD_SPEC = 1063
PARSE_ERROR = 1064
EMPTY_QUERY = 1065
NONUNIQ_TABLE = 1066
INVALID_DEFAULT = 1067
MULTIPLE_PRI_KEY = 1068
TOO_MANY_KEYS = 1069
TOO_MANY_KEY_PARTS = 1070
TOO_LONG_KEY = 1071
KEY_COLUMN_DOES_NOT_EXITS = 1072
BLOB_USED_AS_KEY = 1073
TOO_BIG_FIELDLENGTH = 1074
WRONG_AUTO_KEY = 1075
READY = 1076
NORMAL_SHUTDOWN = 1077
GOT_SIGNAL = 1078
SHUTDOWN_COMPLETE = 1079
FORCING_CLOSE = 1080
IPSOCK_ERROR = 1081
NO_SUCH_INDEX = 1082
WRONG_FIELD_TERMINATORS = 1083
BLOBS_AND_NO_TERMINATED = 1084
TEXTFILE_NOT_READABLE = 1085
FILE_EXISTS_ERROR = 1086
LOAD_INFO = 1087
ALTER_INFO = 1088
WRONG_SUB_KEY = 1089
CANT_REMOVE_ALL_FIELDS = 1090
CANT_DROP_FIELD_OR_KEY = 1091
INSERT_INFO = 1092
UPDATE_TABLE_USED = 1093
NO_SUCH_THREAD = 1094
KILL_DENIED_ERROR = 1095
NO_TABLES_USED = 1096
TOO_BIG_SET = 1097
NO_UNIQUE_LOGFILE = 1098
TABLE_NOT_LOCKED_FOR_WRITE = 1099
TABLE_NOT_LOCKED = 1100
BLOB_CANT_HAVE_DEFAULT = 1101
WRONG_DB_NAME = 1102
WRONG_TABLE_NAME = 1103
TOO_BIG_SELECT = 1104
UNKNOWN_ERROR = 1105
UNKNOWN_PROCEDURE = 1106
WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
WRONG_PARAMETERS_TO_PROCEDURE = 1108
UNKNOWN_TABLE = 1109
FIELD_SPECIFIED_TWICE = 1110
INVALID_GROUP_FUNC_USE = 1111
UNSUPPORTED_EXTENSION = 1112
TABLE_MUST_HAVE_COLUMNS = 1113
RECORD_FILE_FULL = 1114
UNKNOWN_CHARACTER_SET = 1115
TOO_MANY_TABLES = 1116
TOO_MANY_FIELDS = 1117
TOO_BIG_ROWSIZE = 1118
STACK_OVERRUN = 1119
WRONG_OUTER_JOIN = 1120
NULL_COLUMN_IN_INDEX = 1121
CANT_FIND_UDF = 1122
CANT_INITIALIZE_UDF = 1123
UDF_NO_PATHS = 1124
UDF_EXISTS = 1125
CANT_OPEN_LIBRARY = 1126
CANT_FIND_DL_ENTRY = 1127
FUNCTION_NOT_DEFINED = 1128
HOST_IS_BLOCKED = 1129
HOST_NOT_PRIVILEGED = 1130
PASSWORD_ANONYMOUS_USER = 1131
PASSWORD_NOT_ALLOWED = 1132
PASSWORD_NO_MATCH = 1133
UPDATE_INFO = 1134
CANT_CREATE_THREAD = 1135
WRONG_VALUE_COUNT_ON_ROW = 1136
CANT_REOPEN_TABLE = 1137
INVALID_USE_OF_NULL = 1138
REGEXP_ERROR = 1139
MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
NONEXISTING_GRANT = 1141
TABLEACCESS_DENIED_ERROR = 1142
COLUMNACCESS_DENIED_ERROR = 1143
ILLEGAL_GRANT_FOR_TABLE = 1144
GRANT_WRONG_HOST_OR_USER = 1145
NO_SUCH_TABLE = 1146
NONEXISTING_TABLE_GRANT = 1147
NOT_ALLOWED_COMMAND = 1148
SYNTAX_ERROR = 1149
DELAYED_CANT_CHANGE_LOCK = 1150
TOO_MANY_DELAYED_THREADS = 1151
ABORTING_CONNECTION = 1152
NET_PACKET_TOO_LARGE = 1153
NET_READ_ERROR_FROM_PIPE = 1154
NET_FCNTL_ERROR = 1155
NET_PACKETS_OUT_OF_ORDER = 1156
NET_UNCOMPRESS_ERROR = 1157
NET_READ_ERROR = 1158
NET_READ_INTERRUPTED = 1159
NET_ERROR_ON_WRITE = 1160
NET_WRITE_INTERRUPTED = 1161
TOO_LONG_STRING = 1162
TABLE_CANT_HANDLE_BLOB = 1163
TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
DELAYED_INSERT_TABLE_LOCKED = 1165
WRONG_COLUMN_NAME = 1166
WRONG_KEY_COLUMN = 1167
WRONG_MRG_TABLE = 1168
DUP_UNIQUE = 1169
BLOB_KEY_WITHOUT_LENGTH = 1170
PRIMARY_CANT_HAVE_NULL = 1171
TOO_MANY_ROWS = 1172
REQUIRES_PRIMARY_KEY = 1173
NO_RAID_COMPILED = 1174
UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
KEY_DOES_NOT_EXITS = 1176
CHECK_NO_SUCH_TABLE = 1177
CHECK_NOT_IMPLEMENTED = 1178
CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
ERROR_DURING_COMMIT = 1180
ERROR_DURING_ROLLBACK = 1181
ERROR_DURING_FLUSH_LOGS = 1182
ERROR_DURING_CHECKPOINT = 1183
NEW_ABORTING_CONNECTION = 1184
DUMP_NOT_IMPLEMENTED = 1185
FLUSH_MASTER_BINLOG_CLOSED = 1186
INDEX_REBUILD = 1187
MASTER = 1188
MASTER_NET_READ = 1189
MASTER_NET_WRITE = 1190
FT_MATCHING_KEY_NOT_FOUND = 1191
LOCK_OR_ACTIVE_TRANSACTION = 1192
UNKNOWN_SYSTEM_VARIABLE = 1193
CRASHED_ON_USAGE = 1194
CRASHED_ON_REPAIR = 1195
WARNING_NOT_COMPLETE_ROLLBACK = 1196
TRANS_CACHE_FULL = 1197
SLAVE_MUST_STOP = 1198
SLAVE_NOT_RUNNING = 1199
BAD_SLAVE = 1200
MASTER_INFO = 1201
SLAVE_THREAD = 1202
TOO_MANY_USER_CONNECTIONS = 1203
SET_CONSTANTS_ONLY = 1204
LOCK_WAIT_TIMEOUT = 1205
LOCK_TABLE_FULL = 1206
READ_ONLY_TRANSACTION = 1207
DROP_DB_WITH_READ_LOCK = 1208
CREATE_DB_WITH_READ_LOCK = 1209
WRONG_ARGUMENTS = 1210
NO_PERMISSION_TO_CREATE_USER = 1211
UNION_TABLES_IN_DIFFERENT_DIR = 1212
LOCK_DEADLOCK = 1213
TABLE_CANT_HANDLE_FT = 1214
CANNOT_ADD_FOREIGN = 1215
NO_REFERENCED_ROW = 1216
ROW_IS_REFERENCED = 1217
CONNECT_TO_MASTER = 1218
QUERY_ON_MASTER = 1219
ERROR_WHEN_EXECUTING_COMMAND = 1220
WRONG_USAGE = 1221
WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
CANT_UPDATE_WITH_READLOCK = 1223
MIXING_NOT_ALLOWED = 1224
DUP_ARGUMENT = 1225
USER_LIMIT_REACHED = 1226
SPECIFIC_ACCESS_DENIED_ERROR = 1227
LOCAL_VARIABLE = 1228
GLOBAL_VARIABLE = 1229
NO_DEFAULT = 1230
WRONG_VALUE_FOR_VAR = 1231
WRONG_TYPE_FOR_VAR = 1232
VAR_CANT_BE_READ = 1233
CANT_USE_OPTION_HERE = 1234
NOT_SUPPORTED_YET = 1235
MASTER_FATAL_ERROR_READING_BINLOG = 1236
SLAVE_IGNORED_TABLE = 1237
INCORRECT_GLOBAL_LOCAL_VAR = 1238
WRONG_FK_DEF = 1239
KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240
OPERAND_COLUMNS = 1241
SUBQUERY_NO_1_ROW = 1242
UNKNOWN_STMT_HANDLER = 1243
CORRUPT_HELP_DB = 1244
CYCLIC_REFERENCE = 1245
AUTO_CONVERT = 1246
ILLEGAL_REFERENCE = 1247
DERIVED_MUST_HAVE_ALIAS = 1248
SELECT_REDUCED = 1249
TABLENAME_NOT_ALLOWED_HERE = 1250
NOT_SUPPORTED_AUTH_MODE = 1251
SPATIAL_CANT_HAVE_NULL = 1252
COLLATION_CHARSET_MISMATCH = 1253
SLAVE_WAS_RUNNING = 1254
SLAVE_WAS_NOT_RUNNING = 1255
TOO_BIG_FOR_UNCOMPRESS = 1256
ZLIB_Z_MEM_ERROR = 1257
ZLIB_Z_BUF_ERROR = 1258
ZLIB_Z_DATA_ERROR = 1259
CUT_VALUE_GROUP_CONCAT = 1260
WARN_TOO_FEW_RECORDS = 1261
WARN_TOO_MANY_RECORDS = 1262
WARN_NULL_TO_NOTNULL = 1263
WARN_DATA_OUT_OF_RANGE = 1264
WARN_DATA_TRUNCATED = 1265
WARN_USING_OTHER_HANDLER = 1266
CANT_AGGREGATE_2COLLATIONS = 1267
DROP_USER = 1268
REVOKE_GRANTS = 1269
CANT_AGGREGATE_3COLLATIONS = 1270
CANT_AGGREGATE_NCOLLATIONS = 1271
VARIABLE_IS_NOT_STRUCT = 1272
UNKNOWN_COLLATION = 1273
SLAVE_IGNORED_SSL_PARAMS = 1274
SERVER_IS_IN_SECURE_AUTH_MODE = 1275
WARN_FIELD_RESOLVED = 1276
BAD_SLAVE_UNTIL_COND = 1277
MISSING_SKIP_SLAVE = 1278
UNTIL_COND_IGNORED = 1279
WRONG_NAME_FOR_INDEX = 1280
WRONG_NAME_FOR_CATALOG = 1281
WARN_QC_RESIZE = 1282
BAD_FT_COLUMN = 1283
UNKNOWN_KEY_CACHE = 1284
WARN_HOSTNAME_WONT_WORK = 1285
UNKNOWN_STORAGE_ENGINE = 1286
WARN_DEPRECATED_SYNTAX = 1287
NON_UPDATABLE_TABLE = 1288
FEATURE_DISABLED = 1289
OPTION_PREVENTS_STATEMENT = 1290
DUPLICATED_VALUE_IN_TYPE = 1291
TRUNCATED_WRONG_VALUE = 1292
TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293
INVALID_ON_UPDATE = 1294
UNSUPPORTED_PS = 1295
GET_ERRMSG = 1296
GET_TEMPORARY_ERRMSG = 1297
UNKNOWN_TIME_ZONE = 1298
WARN_INVALID_TIMESTAMP = 1299
INVALID_CHARACTER_STRING = 1300
WARN_ALLOWED_PACKET_OVERFLOWED = 1301
CONFLICTING_DECLARATIONS = 1302
SP_NO_RECURSIVE_CREATE = 1303
SP_ALREADY_EXISTS = 1304
SP_DOES_NOT_EXIST = 1305
SP_DROP_FAILED = 1306
SP_STORE_FAILED = 1307
SP_LILABEL_MISMATCH = 1308
SP_LABEL_REDEFINE = 1309
SP_LABEL_MISMATCH = 1310
SP_UNINIT_VAR = 1311
SP_BADSELECT = 1312
SP_BADRETURN = 1313
SP_BADSTATEMENT = 1314
UPDATE_LOG_DEPRECATED_IGNORED = 1315
UPDATE_LOG_DEPRECATED_TRANSLATED = 1316
QUERY_INTERRUPTED = 1317
SP_WRONG_NO_OF_ARGS = 1318
SP_COND_MISMATCH = 1319
SP_NORETURN = 1320
SP_NORETURNEND = 1321
SP_BAD_CURSOR_QUERY = 1322
SP_BAD_CURSOR_SELECT = 1323
SP_CURSOR_MISMATCH = 1324
SP_CURSOR_ALREADY_OPEN = 1325
SP_CURSOR_NOT_OPEN = 1326
SP_UNDECLARED_VAR = 1327
SP_WRONG_NO_OF_FETCH_ARGS = 1328
SP_FETCH_NO_DATA = 1329
SP_DUP_PARAM = 1330
SP_DUP_VAR = 1331
SP_DUP_COND = 1332
SP_DUP_CURS = 1333
SP_CANT_ALTER = 1334
SP_SUBSELECT_NYI = 1335
STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336
SP_VARCOND_AFTER_CURSHNDLR = 1337
SP_CURSOR_AFTER_HANDLER = 1338
SP_CASE_NOT_FOUND = 1339
FPARSER_TOO_BIG_FILE = 1340
FPARSER_BAD_HEADER = 1341
FPARSER_EOF_IN_COMMENT = 1342
FPARSER_ERROR_IN_PARAMETER = 1343
FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344
VIEW_NO_EXPLAIN = 1345
FRM_UNKNOWN_TYPE = 1346
WRONG_OBJECT = 1347
NONUPDATEABLE_COLUMN = 1348
VIEW_SELECT_DERIVED = 1349
VIEW_SELECT_CLAUSE = 1350
VIEW_SELECT_VARIABLE = 1351
VIEW_SELECT_TMPTABLE = 1352
VIEW_WRONG_LIST = 1353
WARN_VIEW_MERGE = 1354
WARN_VIEW_WITHOUT_KEY = 1355
VIEW_INVALID = 1356
SP_NO_DROP_SP = 1357
SP_GOTO_IN_HNDLR = 1358
TRG_ALREADY_EXISTS = 1359
TRG_DOES_NOT_EXIST = 1360
TRG_ON_VIEW_OR_TEMP_TABLE = 1361
TRG_CANT_CHANGE_ROW = 1362
TRG_NO_SUCH_ROW_IN_TRG = 1363
NO_DEFAULT_FOR_FIELD = 1364
DIVISION_BY_ZERO = 1365
TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366
ILLEGAL_VALUE_FOR_TYPE = 1367
VIEW_NONUPD_CHECK = 1368
VIEW_CHECK_FAILED = 1369
PROCACCESS_DENIED_ERROR = 1370
RELAY_LOG_FAIL = 1371
PASSWD_LENGTH = 1372
UNKNOWN_TARGET_BINLOG = 1373
IO_ERR_LOG_INDEX_READ = 1374
BINLOG_PURGE_PROHIBITED = 1375
FSEEK_FAIL = 1376
BINLOG_PURGE_FATAL_ERR = 1377
LOG_IN_USE = 1378
LOG_PURGE_UNKNOWN_ERR = 1379
RELAY_LOG_INIT = 1380
NO_BINARY_LOGGING = 1381
RESERVED_SYNTAX = 1382
WSAS_FAILED = 1383
DIFF_GROUPS_PROC = 1384
NO_GROUP_FOR_PROC = 1385
ORDER_WITH_PROC = 1386
LOGGING_PROHIBIT_CHANGING_OF = 1387
NO_FILE_MAPPING = 1388
WRONG_MAGIC = 1389
PS_MANY_PARAM = 1390
KEY_PART_0 = 1391
VIEW_CHECKSUM = 1392
VIEW_MULTIUPDATE = 1393
VIEW_NO_INSERT_FIELD_LIST = 1394
VIEW_DELETE_MERGE_VIEW = 1395
CANNOT_USER = 1396
XAER_NOTA = 1397
XAER_INVAL = 1398
XAER_RMFAIL = 1399
XAER_OUTSIDE = 1400
XAER_RMERR = 1401
XA_RBROLLBACK = 1402
NONEXISTING_PROC_GRANT = 1403
PROC_AUTO_GRANT_FAIL = 1404
PROC_AUTO_REVOKE_FAIL = 1405
DATA_TOO_LONG = 1406
SP_BAD_SQLSTATE = 1407
STARTUP = 1408
LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409
CANT_CREATE_USER_WITH_GRANT = 1410
WRONG_VALUE_FOR_TYPE = 1411
TABLE_DEF_CHANGED = 1412
SP_DUP_HANDLER = 1413
SP_NOT_VAR_ARG = 1414
SP_NO_RETSET = 1415
CANT_CREATE_GEOMETRY_OBJECT = 1416
FAILED_ROUTINE_BREAK_BINLOG = 1417
BINLOG_UNSAFE_ROUTINE = 1418
BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419
EXEC_STMT_WITH_OPEN_CURSOR = 1420
STMT_HAS_NO_OPEN_CURSOR = 1421
COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422
NO_DEFAULT_FOR_VIEW_FIELD = 1423
SP_NO_RECURSION = 1424
TOO_BIG_SCALE = 1425
TOO_BIG_PRECISION = 1426
M_BIGGER_THAN_D = 1427
WRONG_LOCK_OF_SYSTEM_TABLE = 1428
CONNECT_TO_FOREIGN_DATA_SOURCE = 1429
QUERY_ON_FOREIGN_DATA_SOURCE = 1430
FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431
FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432
FOREIGN_DATA_STRING_INVALID = 1433
CANT_CREATE_FEDERATED_TABLE = 1434
TRG_IN_WRONG_SCHEMA = 1435
STACK_OVERRUN_NEED_MORE = 1436
TOO_LONG_BODY = 1437
WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438
TOO_BIG_DISPLAYWIDTH = 1439
XAER_DUPID = 1440
DATETIME_FUNCTION_OVERFLOW = 1441
CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442
VIEW_PREVENT_UPDATE = 1443
PS_NO_RECURSION = 1444
SP_CANT_SET_AUTOCOMMIT = 1445
MALFORMED_DEFINER = 1446
VIEW_FRM_NO_USER = 1447
VIEW_OTHER_USER = 1448
NO_SUCH_USER = 1449
FORBID_SCHEMA_CHANGE = 1450
ROW_IS_REFERENCED_2 = 1451
NO_REFERENCED_ROW_2 = 1452
SP_BAD_VAR_SHADOW = 1453
TRG_NO_DEFINER = 1454
OLD_FILE_FORMAT = 1455
SP_RECURSION_LIMIT = 1456
SP_PROC_TABLE_CORRUPT = 1457
SP_WRONG_NAME = 1458
TABLE_NEEDS_UPGRADE = 1459
SP_NO_AGGREGATE = 1460
MAX_PREPARED_STMT_COUNT_REACHED = 1461
VIEW_RECURSIVE = 1462
NON_GROUPING_FIELD_USED = 1463
TABLE_CANT_HANDLE_SPKEYS = 1464
NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465
USERNAME = 1466
HOSTNAME = 1467
WRONG_STRING_LENGTH = 1468
ERROR_LAST = 1468
@@ -0,0 +1,32 @@
DECIMAL = 0
TINY = 1
SHORT = 2
LONG = 3
FLOAT = 4
DOUBLE = 5
NULL = 6
TIMESTAMP = 7
LONGLONG = 8
INT24 = 9
DATE = 10
TIME = 11
DATETIME = 12
YEAR = 13
NEWDATE = 14
VARCHAR = 15
BIT = 16
NEWDECIMAL = 246
ENUM = 247
SET = 248
TINY_BLOB = 249
MEDIUM_BLOB = 250
LONG_BLOB = 251
BLOB = 252
VAR_STRING = 253
STRING = 254
GEOMETRY = 255
CHAR = TINY
INTERVAL = ENUM
+15
View File
@@ -0,0 +1,15 @@
NOT_NULL = 1
PRI_KEY = 2
UNIQUE_KEY = 4
MULTIPLE_KEY = 8
BLOB = 16
UNSIGNED = 32
ZEROFILL = 64
BINARY = 128
ENUM = 256
AUTO_INCREMENT = 512
TIMESTAMP = 1024
SET = 2048
PART_KEY = 16384
GROUP = 32767
UNIQUE = 65536
@@ -0,0 +1,12 @@
SERVER_STATUS_IN_TRANS = 1
SERVER_STATUS_AUTOCOMMIT = 2
SERVER_MORE_RESULTS_EXISTS = 8
SERVER_QUERY_NO_GOOD_INDEX_USED = 16
SERVER_QUERY_NO_INDEX_USED = 32
SERVER_STATUS_CURSOR_EXISTS = 64
SERVER_STATUS_LAST_ROW_SENT = 128
SERVER_STATUS_DB_DROPPED = 256
SERVER_STATUS_NO_BACKSLASH_ESCAPES = 512
SERVER_STATUS_METADATA_CHANGED = 1024
+347
View File
@@ -0,0 +1,347 @@
import re
import datetime
import time
from constants import FIELD_TYPE, FLAG
from charset import charset_by_id
try:
set
except NameError:
try:
from sets import BaseSet as set
except ImportError:
from sets import Set as set
ESCAPE_REGEX = re.compile(r"[\0\n\r\032\'\"\\]")
ESCAPE_MAP = {'\0': '\\0', '\n': '\\n', '\r': '\\r', '\032': '\\Z',
'\'': '\\\'', '"': '\\"', '\\': '\\\\'}
def escape_item(val, charset):
if type(val) in [tuple, list, set]:
return escape_sequence(val, charset)
if type(val) is dict:
return escape_dict(val, charset)
if hasattr(val, "decode") and not isinstance(val, unicode):
# deal with py3k bytes
val = val.decode(charset)
encoder = encoders[type(val)]
val = encoder(val)
if type(val) is str:
return val
val = val.encode(charset)
return val
def escape_dict(val, charset):
n = {}
for k, v in val.items():
quoted = escape_item(v, charset)
n[k] = quoted
return n
def escape_sequence(val, charset):
n = []
for item in val:
quoted = escape_item(item, charset)
n.append(quoted)
return tuple(n)
def escape_set(val, charset):
val = map(lambda x: escape_item(x, charset), val)
return ','.join(val)
def escape_bool(value):
return str(int(value))
def escape_object(value):
return str(value)
escape_int = escape_long = escape_object
def escape_float(value):
return ('%.15g' % value)
def escape_string(value):
return ("'%s'" % ESCAPE_REGEX.sub(
lambda match: ESCAPE_MAP.get(match.group(0)), value))
def escape_unicode(value):
return escape_string(value)
def escape_None(value):
return 'NULL'
def escape_timedelta(obj):
seconds = int(obj.seconds) % 60
minutes = int(obj.seconds // 60) % 60
hours = int(obj.seconds // 3600) % 24 + int(obj.days) * 24
return escape_string('%02d:%02d:%02d' % (hours, minutes, seconds))
def escape_time(obj):
s = "%02d:%02d:%02d" % (int(obj.hour), int(obj.minute),
int(obj.second))
if obj.microsecond:
s += ".%f" % obj.microsecond
return escape_string(s)
def escape_datetime(obj):
return escape_string(obj.strftime("%Y-%m-%d %H:%M:%S"))
def escape_date(obj):
return escape_string(obj.strftime("%Y-%m-%d"))
def escape_struct_time(obj):
return escape_datetime(datetime.datetime(*obj[:6]))
def convert_datetime(connection, field, obj):
"""Returns a DATETIME or TIMESTAMP column value as a datetime object:
>>> datetime_or_None('2007-02-25 23:06:20')
datetime.datetime(2007, 2, 25, 23, 6, 20)
>>> datetime_or_None('2007-02-25T23:06:20')
datetime.datetime(2007, 2, 25, 23, 6, 20)
Illegal values are returned as None:
>>> datetime_or_None('2007-02-31T23:06:20') is None
True
>>> datetime_or_None('0000-00-00 00:00:00') is None
True
"""
if not isinstance(obj, unicode):
obj = obj.decode(connection.charset)
if ' ' in obj:
sep = ' '
elif 'T' in obj:
sep = 'T'
else:
return convert_date(connection, field, obj)
try:
ymd, hms = obj.split(sep, 1)
return datetime.datetime(*[ int(x) for x in ymd.split('-')+hms.split(':') ])
except ValueError:
return convert_date(connection, field, obj)
def convert_timedelta(connection, field, obj):
"""Returns a TIME column as a timedelta object:
>>> timedelta_or_None('25:06:17')
datetime.timedelta(1, 3977)
>>> timedelta_or_None('-25:06:17')
datetime.timedelta(-2, 83177)
Illegal values are returned as None:
>>> timedelta_or_None('random crap') is None
True
Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but
can accept values as (+|-)DD HH:MM:SS. The latter format will not
be parsed correctly by this function.
"""
from math import modf
try:
if not isinstance(obj, unicode):
obj = obj.decode(connection.charset)
hours, minutes, seconds = tuple([int(x) for x in obj.split(':')])
tdelta = datetime.timedelta(
hours = int(hours),
minutes = int(minutes),
seconds = int(seconds),
microseconds = int(modf(float(seconds))[0]*1000000),
)
return tdelta
except ValueError:
return None
def convert_time(connection, field, obj):
"""Returns a TIME column as a time object:
>>> time_or_None('15:06:17')
datetime.time(15, 6, 17)
Illegal values are returned as None:
>>> time_or_None('-25:06:17') is None
True
>>> time_or_None('random crap') is None
True
Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but
can accept values as (+|-)DD HH:MM:SS. The latter format will not
be parsed correctly by this function.
Also note that MySQL's TIME column corresponds more closely to
Python's timedelta and not time. However if you want TIME columns
to be treated as time-of-day and not a time offset, then you can
use set this function as the converter for FIELD_TYPE.TIME.
"""
from math import modf
try:
hour, minute, second = obj.split(':')
return datetime.time(hour=int(hour), minute=int(minute),
second=int(second),
microsecond=int(modf(float(second))[0]*1000000))
except ValueError:
return None
def convert_date(connection, field, obj):
"""Returns a DATE column as a date object:
>>> date_or_None('2007-02-26')
datetime.date(2007, 2, 26)
Illegal values are returned as None:
>>> date_or_None('2007-02-31') is None
True
>>> date_or_None('0000-00-00') is None
True
"""
try:
if not isinstance(obj, unicode):
obj = obj.decode(connection.charset)
return datetime.date(*[ int(x) for x in obj.split('-', 2) ])
except ValueError:
return None
def convert_mysql_timestamp(connection, field, timestamp):
"""Convert a MySQL TIMESTAMP to a Timestamp object.
MySQL >= 4.1 returns TIMESTAMP in the same format as DATETIME:
>>> mysql_timestamp_converter('2007-02-25 22:32:17')
datetime.datetime(2007, 2, 25, 22, 32, 17)
MySQL < 4.1 uses a big string of numbers:
>>> mysql_timestamp_converter('20070225223217')
datetime.datetime(2007, 2, 25, 22, 32, 17)
Illegal values are returned as None:
>>> mysql_timestamp_converter('2007-02-31 22:32:17') is None
True
>>> mysql_timestamp_converter('00000000000000') is None
True
"""
if not isinstance(timestamp, unicode):
timestamp = timestamp.decode(connection.charset)
if timestamp[4] == '-':
return convert_datetime(connection, field, timestamp)
timestamp += "0"*(14-len(timestamp)) # padding
year, month, day, hour, minute, second = \
int(timestamp[:4]), int(timestamp[4:6]), int(timestamp[6:8]), \
int(timestamp[8:10]), int(timestamp[10:12]), int(timestamp[12:14])
try:
return datetime.datetime(year, month, day, hour, minute, second)
except ValueError:
return None
def convert_set(s):
return set(s.split(","))
def convert_bit(connection, field, b):
#b = "\x00" * (8 - len(b)) + b # pad w/ zeroes
#return struct.unpack(">Q", b)[0]
#
# the snippet above is right, but MySQLdb doesn't process bits,
# so we shouldn't either
return b
def convert_characters(connection, field, data):
field_charset = charset_by_id(field.charsetnr).name
if field.flags & FLAG.SET:
return convert_set(data.decode(field_charset))
if field.flags & FLAG.BINARY:
return data
if connection.use_unicode:
data = data.decode(field_charset)
elif connection.charset != field_charset:
data = data.decode(field_charset)
data = data.encode(connection.charset)
else:
data = data.decode(connection.charset)
return data
def convert_int(connection, field, data):
return int(data)
def convert_long(connection, field, data):
return long(data)
def convert_float(connection, field, data):
return float(data)
encoders = {
bool: escape_bool,
int: escape_int,
long: escape_long,
float: escape_float,
str: escape_string,
unicode: escape_unicode,
tuple: escape_sequence,
list:escape_sequence,
set:escape_sequence,
dict:escape_dict,
type(None):escape_None,
datetime.date: escape_date,
datetime.datetime : escape_datetime,
datetime.timedelta : escape_timedelta,
datetime.time : escape_time,
time.struct_time : escape_struct_time,
}
decoders = {
FIELD_TYPE.BIT: convert_bit,
FIELD_TYPE.TINY: convert_int,
FIELD_TYPE.SHORT: convert_int,
FIELD_TYPE.LONG: convert_long,
FIELD_TYPE.FLOAT: convert_float,
FIELD_TYPE.DOUBLE: convert_float,
FIELD_TYPE.DECIMAL: convert_float,
FIELD_TYPE.NEWDECIMAL: convert_float,
FIELD_TYPE.LONGLONG: convert_long,
FIELD_TYPE.INT24: convert_int,
FIELD_TYPE.YEAR: convert_int,
FIELD_TYPE.TIMESTAMP: convert_mysql_timestamp,
FIELD_TYPE.DATETIME: convert_datetime,
FIELD_TYPE.TIME: convert_timedelta,
FIELD_TYPE.DATE: convert_date,
FIELD_TYPE.SET: convert_set,
FIELD_TYPE.BLOB: convert_characters,
FIELD_TYPE.TINY_BLOB: convert_characters,
FIELD_TYPE.MEDIUM_BLOB: convert_characters,
FIELD_TYPE.LONG_BLOB: convert_characters,
FIELD_TYPE.STRING: convert_characters,
FIELD_TYPE.VAR_STRING: convert_characters,
FIELD_TYPE.VARCHAR: convert_characters,
#FIELD_TYPE.BLOB: str,
#FIELD_TYPE.STRING: str,
#FIELD_TYPE.VAR_STRING: str,
#FIELD_TYPE.VARCHAR: str
}
conversions = decoders # for MySQLdb compatibility
try:
# python version > 2.3
from decimal import Decimal
def convert_decimal(connection, field, data):
return Decimal(data)
decoders[FIELD_TYPE.DECIMAL] = convert_decimal
decoders[FIELD_TYPE.NEWDECIMAL] = convert_decimal
def escape_decimal(obj):
return unicode(obj)
encoders[Decimal] = escape_decimal
except ImportError:
pass
+251
View File
@@ -0,0 +1,251 @@
# -*- coding: utf-8 -*-
import struct
import re
try:
import cStringIO as StringIO
except ImportError:
import StringIO
from err import Warning, Error, InterfaceError, DataError, \
DatabaseError, OperationalError, IntegrityError, InternalError, \
NotSupportedError, ProgrammingError
insert_values = re.compile(r'\svalues\s*(\(.+\))', re.IGNORECASE)
class Cursor(object):
'''
This is the object you use to interact with the database.
'''
def __init__(self, connection):
'''
Do not create an instance of a Cursor yourself. Call
connections.Connection.cursor().
'''
from weakref import proxy
self.connection = proxy(connection)
self.description = None
self.rownumber = 0
self.rowcount = -1
self.arraysize = 1
self._executed = None
self.messages = []
self.errorhandler = connection.errorhandler
self._has_next = None
self._rows = ()
def __del__(self):
'''
When this gets GC'd close it.
'''
self.close()
def close(self):
'''
Closing a cursor just exhausts all remaining data.
'''
if not self.connection:
return
try:
while self.nextset():
pass
except:
pass
self.connection = None
def _get_db(self):
if not self.connection:
self.errorhandler(self, ProgrammingError, "cursor closed")
return self.connection
def _check_executed(self):
if not self._executed:
self.errorhandler(self, ProgrammingError, "execute() first")
def setinputsizes(self, *args):
"""Does nothing, required by DB API."""
def setoutputsizes(self, *args):
"""Does nothing, required by DB API."""
def nextset(self):
''' Get the next query set '''
if self._executed:
self.fetchall()
del self.messages[:]
if not self._has_next:
return None
connection = self._get_db()
connection.next_result()
self._do_get_result()
return True
def execute(self, query, args=None):
''' Execute a query '''
from sys import exc_info
conn = self._get_db()
charset = conn.charset
del self.messages[:]
# TODO: make sure that conn.escape is correct
if args is not None:
query = query % conn.escape(args)
if isinstance(query, unicode):
query = query.encode(charset)
result = 0
try:
result = self._query(query)
except:
exc, value, tb = exc_info()
del tb
self.messages.append((exc,value))
self.errorhandler(self, exc, value)
self._executed = query
return result
def executemany(self, query, args):
''' Run several data against one query '''
del self.messages[:]
conn = self._get_db()
if not args:
return
charset = conn.charset
if isinstance(query, unicode):
query = query.encode(charset)
self.rowcount = sum([ self.execute(query, arg) for arg in args ])
return self.rowcount
def callproc(self, procname, args=()):
"""Execute stored procedure procname with args
procname -- string, name of procedure to execute on server
args -- Sequence of parameters to use with procedure
Returns the original args.
Compatibility warning: PEP-249 specifies that any modified
parameters must be returned. This is currently impossible
as they are only available by storing them in a server
variable and then retrieved by a query. Since stored
procedures return zero or more result sets, there is no
reliable way to get at OUT or INOUT parameters via callproc.
The server variables are named @_procname_n, where procname
is the parameter above and n is the position of the parameter
(from zero). Once all result sets generated by the procedure
have been fetched, you can issue a SELECT @_procname_0, ...
query using .execute() to get any OUT or INOUT values.
Compatibility warning: The act of calling a stored procedure
itself creates an empty result set. This appears after any
result sets generated by the procedure. This is non-standard
behavior with respect to the DB-API. Be sure to use nextset()
to advance through all result sets; otherwise you may get
disconnected.
"""
conn = self._get_db()
for index, arg in enumerate(args):
q = "SET @_%s_%d=%s" % (procname, index, conn.escape(arg))
if isinstance(q, unicode):
q = q.encode(conn.charset)
self._query(q)
self.nextset()
q = "CALL %s(%s)" % (procname,
','.join(['@_%s_%d' % (procname, i)
for i in range(len(args))]))
if isinstance(q, unicode):
q = q.encode(conn.charset)
self._query(q)
self._executed = q
return args
def fetchone(self):
''' Fetch the next row '''
self._check_executed()
if self._rows is None or self.rownumber >= len(self._rows):
return None
result = self._rows[self.rownumber]
self.rownumber += 1
return result
def fetchmany(self, size=None):
''' Fetch several rows '''
self._check_executed()
end = self.rownumber + (size or self.arraysize)
result = self._rows[self.rownumber:end]
if self._rows is None:
return None
self.rownumber = min(end, len(self._rows))
return result
def fetchall(self):
''' Fetch all the rows '''
self._check_executed()
if self._rows is None:
return None
if self.rownumber:
result = self._rows[self.rownumber:]
else:
result = self._rows
self.rownumber = len(self._rows)
return result
def scroll(self, value, mode='relative'):
self._check_executed()
if mode == 'relative':
r = self.rownumber + value
elif mode == 'absolute':
r = value
else:
self.errorhandler(self, ProgrammingError,
"unknown scroll mode %s" % mode)
if r < 0 or r >= len(self._rows):
self.errorhandler(self, IndexError, "out of range")
self.rownumber = r
def _query(self, q):
conn = self._get_db()
self._last_executed = q
conn.query(q)
self._do_get_result()
return self.rowcount
def _do_get_result(self):
conn = self._get_db()
self.rowcount = conn._result.affected_rows
self.rownumber = 0
self.description = conn._result.description
self.lastrowid = conn._result.insert_id
self._rows = conn._result.rows
self._has_next = conn._result.has_next
conn._result = None
def __iter__(self):
self._check_executed()
result = self.rownumber and self._rows[self.rownumber:] or self._rows
return iter(result)
Warning = Warning
Error = Error
InterfaceError = InterfaceError
DatabaseError = DatabaseError
DataError = DataError
OperationalError = OperationalError
IntegrityError = IntegrityError
InternalError = InternalError
ProgrammingError = ProgrammingError
NotSupportedError = NotSupportedError
+141
View File
@@ -0,0 +1,141 @@
import struct
try:
Exception, Warning
except ImportError:
try:
from exceptions import Exception, Warning
except ImportError:
import sys
e = sys.modules['exceptions']
Exception = e.Exception
Warning = e.Warning
from constants import ER
class MySQLError(Exception):
"""Exception related to operation with MySQL."""
class Warning(Warning, MySQLError):
"""Exception raised for important warnings like data truncations
while inserting, etc."""
class Error(MySQLError):
"""Exception that is the base class of all other error exceptions
(not Warning)."""
class InterfaceError(Error):
"""Exception raised for errors that are related to the database
interface rather than the database itself."""
class DatabaseError(Error):
"""Exception raised for errors that are related to the
database."""
class DataError(DatabaseError):
"""Exception raised for errors that are due to problems with the
processed data like division by zero, numeric value out of range,
etc."""
class OperationalError(DatabaseError):
"""Exception raised for errors that are related to the database's
operation and not necessarily under the control of the programmer,
e.g. an unexpected disconnect occurs, the data source name is not
found, a transaction could not be processed, a memory allocation
error occurred during processing, etc."""
class IntegrityError(DatabaseError):
"""Exception raised when the relational integrity of the database
is affected, e.g. a foreign key check fails, duplicate key,
etc."""
class InternalError(DatabaseError):
"""Exception raised when the database encounters an internal
error, e.g. the cursor is not valid anymore, the transaction is
out of sync, etc."""
class ProgrammingError(DatabaseError):
"""Exception raised for programming errors, e.g. table not found
or already exists, syntax error in the SQL statement, wrong number
of parameters specified, etc."""
class NotSupportedError(DatabaseError):
"""Exception raised in case a method or database API was used
which is not supported by the database, e.g. requesting a
.rollback() on a connection that does not support transaction or
has transactions turned off."""
error_map = {}
def _map_error(exc, *errors):
for error in errors:
error_map[error] = exc
_map_error(ProgrammingError, ER.DB_CREATE_EXISTS, ER.SYNTAX_ERROR,
ER.PARSE_ERROR, ER.NO_SUCH_TABLE, ER.WRONG_DB_NAME,
ER.WRONG_TABLE_NAME, ER.FIELD_SPECIFIED_TWICE,
ER.INVALID_GROUP_FUNC_USE, ER.UNSUPPORTED_EXTENSION,
ER.TABLE_MUST_HAVE_COLUMNS, ER.CANT_DO_THIS_DURING_AN_TRANSACTION)
_map_error(DataError, ER.WARN_DATA_TRUNCATED, ER.WARN_NULL_TO_NOTNULL,
ER.WARN_DATA_OUT_OF_RANGE, ER.NO_DEFAULT, ER.PRIMARY_CANT_HAVE_NULL,
ER.DATA_TOO_LONG, ER.DATETIME_FUNCTION_OVERFLOW)
_map_error(IntegrityError, ER.DUP_ENTRY, ER.NO_REFERENCED_ROW,
ER.NO_REFERENCED_ROW_2, ER.ROW_IS_REFERENCED, ER.ROW_IS_REFERENCED_2,
ER.CANNOT_ADD_FOREIGN)
_map_error(NotSupportedError, ER.WARNING_NOT_COMPLETE_ROLLBACK,
ER.NOT_SUPPORTED_YET, ER.FEATURE_DISABLED, ER.UNKNOWN_STORAGE_ENGINE)
del _map_error, ER
def _get_error_info(data):
errno = struct.unpack('<h', data[1:3])[0]
if data[3] == "#":
# version 4.1
sqlstate = data[4:9].decode("utf8")
errorvalue = data[9:].decode("utf8")
return (errno, sqlstate, errorvalue)
else:
# version 4.0
return (errno, None, data[3:].decode("utf8"))
def _check_mysql_exception(errinfo):
errno, sqlstate, errorvalue = errinfo
errorclass = error_map.get(errno, None)
if errorclass:
raise errorclass, (errno,errorvalue)
# couldn't find the right error number
raise InternalError, (errno, errorvalue)
def raise_mysql_exception(data):
errinfo = _get_error_info(data)
_check_mysql_exception(errinfo)
+7
View File
@@ -0,0 +1,7 @@
from pymysql.tests.test_issues import *
from pymysql.tests.test_example import *
from pymysql.tests.test_basic import *
if __name__ == "__main__":
import unittest
unittest.main()
+19
View File
@@ -0,0 +1,19 @@
import pymysql
import unittest
class PyMySQLTestCase(unittest.TestCase):
databases = [
{"host":"localhost","user":"root",
"passwd":"","db":"test_pymysql", "use_unicode": True},
{"host":"localhost","user":"root","passwd":"","db":"test_pymysql2"}]
def setUp(self):
self.connections = []
for params in self.databases:
self.connections.append(pymysql.connect(**params))
def tearDown(self):
for connection in self.connections:
connection.close()
+141
View File
@@ -0,0 +1,141 @@
from pymysql.tests import base
from pymysql import util
import time
import datetime
class TestConversion(base.PyMySQLTestCase):
def test_datatypes(self):
""" test every data type """
conn = self.connections[0]
c = conn.cursor()
c.execute("create table test_datatypes (b bit, i int, l bigint, f real, s varchar(32), u varchar(32), bb blob, d date, dt datetime, ts timestamp, td time, t time, st datetime)")
try:
# insert values
v = (True, -3, 123456789012, 5.7, "hello'\" world", u"Espa\xc3\xb1ol", "binary\x00data".encode(conn.charset), datetime.date(1988,2,2), datetime.datetime.now(), datetime.timedelta(5,6), datetime.time(16,32), time.localtime())
c.execute("insert into test_datatypes (b,i,l,f,s,u,bb,d,dt,td,t,st) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", v)
c.execute("select b,i,l,f,s,u,bb,d,dt,td,t,st from test_datatypes")
r = c.fetchone()
self.assertEqual(util.int2byte(1), r[0])
self.assertEqual(v[1:8], r[1:8])
# mysql throws away microseconds so we need to check datetimes
# specially. additionally times are turned into timedeltas.
self.assertEqual(datetime.datetime(*v[8].timetuple()[:6]), r[8])
self.assertEqual(v[9], r[9]) # just timedeltas
self.assertEqual(datetime.timedelta(0, 60 * (v[10].hour * 60 + v[10].minute)), r[10])
self.assertEqual(datetime.datetime(*v[-1][:6]), r[-1])
c.execute("delete from test_datatypes")
# check nulls
c.execute("insert into test_datatypes (b,i,l,f,s,u,bb,d,dt,td,t,st) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", [None] * 12)
c.execute("select b,i,l,f,s,u,bb,d,dt,td,t,st from test_datatypes")
r = c.fetchone()
self.assertEqual(tuple([None] * 12), r)
c.execute("delete from test_datatypes")
# check sequence type
c.execute("insert into test_datatypes (i, l) values (2,4), (6,8), (10,12)")
c.execute("select l from test_datatypes where i in %s order by i", ((2,6),))
r = c.fetchall()
self.assertEqual(((4,),(8,)), r)
finally:
c.execute("drop table test_datatypes")
def test_dict(self):
""" test dict escaping """
conn = self.connections[0]
c = conn.cursor()
c.execute("create table test_dict (a integer, b integer, c integer)")
try:
c.execute("insert into test_dict (a,b,c) values (%(a)s, %(b)s, %(c)s)", {"a":1,"b":2,"c":3})
c.execute("select a,b,c from test_dict")
self.assertEqual((1,2,3), c.fetchone())
finally:
c.execute("drop table test_dict")
def test_big_blob(self):
""" test tons of data """
conn = self.connections[0]
c = conn.cursor()
c.execute("create table test_big_blob (b blob)")
try:
data = "pymysql" * 1024
c.execute("insert into test_big_blob (b) values (%s)", (data,))
c.execute("select b from test_big_blob")
self.assertEqual(data.encode(conn.charset), c.fetchone()[0])
finally:
c.execute("drop table test_big_blob")
class TestCursor(base.PyMySQLTestCase):
# this test case does not work quite right yet, however,
# we substitute in None for the erroneous field which is
# compatible with the DB-API 2.0 spec and has not broken
# any unit tests for anything we've tried.
#def test_description(self):
# """ test description attribute """
# # result is from MySQLdb module
# r = (('Host', 254, 11, 60, 60, 0, 0),
# ('User', 254, 16, 16, 16, 0, 0),
# ('Password', 254, 41, 41, 41, 0, 0),
# ('Select_priv', 254, 1, 1, 1, 0, 0),
# ('Insert_priv', 254, 1, 1, 1, 0, 0),
# ('Update_priv', 254, 1, 1, 1, 0, 0),
# ('Delete_priv', 254, 1, 1, 1, 0, 0),
# ('Create_priv', 254, 1, 1, 1, 0, 0),
# ('Drop_priv', 254, 1, 1, 1, 0, 0),
# ('Reload_priv', 254, 1, 1, 1, 0, 0),
# ('Shutdown_priv', 254, 1, 1, 1, 0, 0),
# ('Process_priv', 254, 1, 1, 1, 0, 0),
# ('File_priv', 254, 1, 1, 1, 0, 0),
# ('Grant_priv', 254, 1, 1, 1, 0, 0),
# ('References_priv', 254, 1, 1, 1, 0, 0),
# ('Index_priv', 254, 1, 1, 1, 0, 0),
# ('Alter_priv', 254, 1, 1, 1, 0, 0),
# ('Show_db_priv', 254, 1, 1, 1, 0, 0),
# ('Super_priv', 254, 1, 1, 1, 0, 0),
# ('Create_tmp_table_priv', 254, 1, 1, 1, 0, 0),
# ('Lock_tables_priv', 254, 1, 1, 1, 0, 0),
# ('Execute_priv', 254, 1, 1, 1, 0, 0),
# ('Repl_slave_priv', 254, 1, 1, 1, 0, 0),
# ('Repl_client_priv', 254, 1, 1, 1, 0, 0),
# ('Create_view_priv', 254, 1, 1, 1, 0, 0),
# ('Show_view_priv', 254, 1, 1, 1, 0, 0),
# ('Create_routine_priv', 254, 1, 1, 1, 0, 0),
# ('Alter_routine_priv', 254, 1, 1, 1, 0, 0),
# ('Create_user_priv', 254, 1, 1, 1, 0, 0),
# ('Event_priv', 254, 1, 1, 1, 0, 0),
# ('Trigger_priv', 254, 1, 1, 1, 0, 0),
# ('ssl_type', 254, 0, 9, 9, 0, 0),
# ('ssl_cipher', 252, 0, 65535, 65535, 0, 0),
# ('x509_issuer', 252, 0, 65535, 65535, 0, 0),
# ('x509_subject', 252, 0, 65535, 65535, 0, 0),
# ('max_questions', 3, 1, 11, 11, 0, 0),
# ('max_updates', 3, 1, 11, 11, 0, 0),
# ('max_connections', 3, 1, 11, 11, 0, 0),
# ('max_user_connections', 3, 1, 11, 11, 0, 0))
# conn = self.connections[0]
# c = conn.cursor()
# c.execute("select * from mysql.user")
#
# self.assertEqual(r, c.description)
def test_fetch_no_result(self):
""" test a fetchone() with no rows """
conn = self.connections[0]
c = conn.cursor()
c.execute("create table test_nr (b varchar(32))")
try:
data = "pymysql"
c.execute("insert into test_nr (b) values (%s)", (data,))
self.assertEqual(None, c.fetchone())
finally:
c.execute("drop table test_nr")
__all__ = ["TestConversion","TestCursor"]
if __name__ == "__main__":
import unittest
unittest.main()
@@ -0,0 +1,32 @@
import pymysql
from pymysql.tests import base
class TestExample(base.PyMySQLTestCase):
def test_example(self):
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='', db='mysql')
cur = conn.cursor()
cur.execute("SELECT Host,User FROM user")
# print cur.description
# r = cur.fetchall()
# print r
# ...or...
u = False
for r in cur.fetchall():
u = u or conn.user in r
self.assertTrue(u)
cur.close()
conn.close()
__all__ = ["TestExample"]
if __name__ == "__main__":
import unittest
unittest.main()
+240
View File
@@ -0,0 +1,240 @@
import pymysql
from pymysql.tests import base
import sys
try:
import imp
reload = imp.reload
except AttributeError:
pass
import datetime
class TestOldIssues(base.PyMySQLTestCase):
def test_issue_3(self):
""" undefined methods datetime_or_None, date_or_None """
conn = self.connections[0]
c = conn.cursor()
c.execute("create table issue3 (d date, t time, dt datetime, ts timestamp)")
try:
c.execute("insert into issue3 (d, t, dt, ts) values (%s,%s,%s,%s)", (None, None, None, None))
c.execute("select d from issue3")
self.assertEqual(None, c.fetchone()[0])
c.execute("select t from issue3")
self.assertEqual(None, c.fetchone()[0])
c.execute("select dt from issue3")
self.assertEqual(None, c.fetchone()[0])
c.execute("select ts from issue3")
self.assertTrue(isinstance(c.fetchone()[0], datetime.datetime))
finally:
c.execute("drop table issue3")
def test_issue_4(self):
""" can't retrieve TIMESTAMP fields """
conn = self.connections[0]
c = conn.cursor()
c.execute("create table issue4 (ts timestamp)")
try:
c.execute("insert into issue4 (ts) values (now())")
c.execute("select ts from issue4")
self.assertTrue(isinstance(c.fetchone()[0], datetime.datetime))
finally:
c.execute("drop table issue4")
def test_issue_5(self):
""" query on information_schema.tables fails """
con = self.connections[0]
cur = con.cursor()
cur.execute("select * from information_schema.tables")
def test_issue_6(self):
""" exception: TypeError: ord() expected a character, but string of length 0 found """
conn = pymysql.connect(host="localhost",user="root",passwd="",db="mysql")
c = conn.cursor()
c.execute("select * from user")
conn.close()
def test_issue_8(self):
""" Primary Key and Index error when selecting data """
conn = self.connections[0]
c = conn.cursor()
c.execute("""CREATE TABLE `test` (`station` int(10) NOT NULL DEFAULT '0', `dh`
datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `echeance` int(1) NOT NULL
DEFAULT '0', `me` double DEFAULT NULL, `mo` double DEFAULT NULL, PRIMARY
KEY (`station`,`dh`,`echeance`)) ENGINE=MyISAM DEFAULT CHARSET=latin1;""")
try:
self.assertEqual(0, c.execute("SELECT * FROM test"))
c.execute("ALTER TABLE `test` ADD INDEX `idx_station` (`station`)")
self.assertEqual(0, c.execute("SELECT * FROM test"))
finally:
c.execute("drop table test")
def test_issue_9(self):
""" sets DeprecationWarning in Python 2.6 """
try:
reload(pymysql)
except DeprecationWarning:
self.fail()
def test_issue_10(self):
""" Allocate a variable to return when the exception handler is permissive """
conn = self.connections[0]
conn.errorhandler = lambda cursor, errorclass, errorvalue: None
cur = conn.cursor()
cur.execute( "create table t( n int )" )
cur.execute( "create table t( n int )" )
def test_issue_13(self):
""" can't handle large result fields """
conn = self.connections[0]
cur = conn.cursor()
cur.execute("create table issue13 (t text)")
try:
# ticket says 18k
size = 18*1024
cur.execute("insert into issue13 (t) values (%s)", ("x" * size,))
cur.execute("select t from issue13")
# use assert_ so that obscenely huge error messages don't print
r = cur.fetchone()[0]
self.assert_("x" * size == r)
finally:
cur.execute("drop table issue13")
def test_issue_14(self):
""" typo in converters.py """
self.assertEqual('1', pymysql.converters.escape_item(1, "utf8"))
self.assertEqual('1', pymysql.converters.escape_item(1L, "utf8"))
self.assertEqual('1', pymysql.converters.escape_object(1))
self.assertEqual('1', pymysql.converters.escape_object(1L))
def test_issue_15(self):
""" query should be expanded before perform character encoding """
conn = self.connections[0]
c = conn.cursor()
c.execute("create table issue15 (t varchar(32))")
try:
c.execute("insert into issue15 (t) values (%s)", (u'\xe4\xf6\xfc'))
c.execute("select t from issue15")
self.assertEqual(u'\xe4\xf6\xfc', c.fetchone()[0])
finally:
c.execute("drop table issue15")
def test_issue_16(self):
""" Patch for string and tuple escaping """
conn = self.connections[0]
c = conn.cursor()
c.execute("create table issue16 (name varchar(32) primary key, email varchar(32))")
try:
c.execute("insert into issue16 (name, email) values ('pete', 'floydophone')")
c.execute("select email from issue16 where name=%s", ("pete",))
self.assertEqual("floydophone", c.fetchone()[0])
finally:
c.execute("drop table issue16")
def test_issue_17(self):
""" could not connect mysql use passwod """
conn = self.connections[0]
host = self.databases[0]["host"]
db = self.databases[0]["db"]
c = conn.cursor()
# grant access to a table to a user with a password
try:
c.execute("create table issue17 (x varchar(32) primary key)")
c.execute("insert into issue17 (x) values ('hello, world!')")
c.execute("grant all privileges on %s.issue17 to 'issue17user'@'%%' identified by '1234'" % db)
conn.commit()
conn2 = pymysql.connect(host=host, user="issue17user", passwd="1234", db=db)
c2 = conn2.cursor()
c2.execute("select x from issue17")
self.assertEqual("hello, world!", c2.fetchone()[0])
finally:
c.execute("drop table issue17")
def _uni(s, e):
# hack for py3
if sys.version_info[0] > 2:
return unicode(bytes(s, sys.getdefaultencoding()), e)
else:
return unicode(s, e)
class TestNewIssues(base.PyMySQLTestCase):
def test_issue_34(self):
try:
pymysql.connect(host="localhost", port=1237, user="root")
self.fail()
except pymysql.OperationalError, e:
self.assertEqual(2003, e.args[0])
except:
self.fail()
def test_issue_33(self):
conn = pymysql.connect(host="localhost", user="root", db=self.databases[0]["db"], charset="utf8")
c = conn.cursor()
try:
c.execute(_uni("create table hei\xc3\x9fe (name varchar(32))", "utf8"))
c.execute(_uni("insert into hei\xc3\x9fe (name) values ('Pi\xc3\xb1ata')", "utf8"))
c.execute(_uni("select name from hei\xc3\x9fe", "utf8"))
self.assertEqual(_uni("Pi\xc3\xb1ata","utf8"), c.fetchone()[0])
finally:
c.execute(_uni("drop table hei\xc3\x9fe", "utf8"))
# Will fail without manual intervention:
#def test_issue_35(self):
#
# conn = self.connections[0]
# c = conn.cursor()
# print "sudo killall -9 mysqld within the next 10 seconds"
# try:
# c.execute("select sleep(10)")
# self.fail()
# except pymysql.OperationalError, e:
# self.assertEqual(2013, e.args[0])
def test_issue_36(self):
conn = self.connections[0]
c = conn.cursor()
# kill connections[0]
original_count = c.execute("show processlist")
kill_id = None
for id,user,host,db,command,time,state,info in c.fetchall():
if info == "show processlist":
kill_id = id
break
# now nuke the connection
conn.kill(kill_id)
# make sure this connection has broken
try:
c.execute("show tables")
self.fail()
except:
pass
# check the process list from the other connection
self.assertEqual(original_count - 1, self.connections[1].cursor().execute("show processlist"))
del self.connections[0]
def test_issue_37(self):
conn = self.connections[0]
c = conn.cursor()
self.assertEqual(1, c.execute("SELECT @foo"))
self.assertEqual((None,), c.fetchone())
self.assertEqual(0, c.execute("SET @foo = 'bar'"))
c.execute("set @foo = 'bar'")
def test_issue_38(self):
conn = self.connections[0]
c = conn.cursor()
datum = "a" * 1024 * 1023 # reduced size for most default mysql installs
try:
c.execute("create table issue38 (id integer, data mediumblob)")
c.execute("insert into issue38 values (1, %s)", datum)
finally:
c.execute("drop table issue38")
__all__ = ["TestOldIssues", "TestNewIssues"]
if __name__ == "__main__":
import unittest
unittest.main()
+17
View File
@@ -0,0 +1,17 @@
from time import localtime
from datetime import date, datetime, time, timedelta
Date = date
Time = time
TimeDelta = timedelta
Timestamp = datetime
def DateFromTicks(ticks):
return date(*localtime(ticks)[:3])
def TimeFromTicks(ticks):
return time(*localtime(ticks)[3:6])
def TimestampFromTicks(ticks):
return datetime(*localtime(ticks)[:6])
+20
View File
@@ -0,0 +1,20 @@
import struct
def byte2int(b):
if isinstance(b, int):
return b
else:
return struct.unpack("!B", b)[0]
def int2byte(i):
return struct.pack("!B", i)
def join_bytes(bs):
if len(bs) == 0:
return ""
else:
rv = bs[0]
for b in bs[1:]:
rv += b
return rv
+158
View File
@@ -0,0 +1,158 @@
class ViewKind :
"""An integer (0-5) that represents the view mode of the document."""
NONE = 0
PageLayout = 1
Outline = 2
MasterDocument = 3
Normal = 4
OnlineLayout = 5
DEFAULT = PageLayout
def _IsValid( cls, value ) :
return value in [ 0, 1, 2, 3, 4, 5 ]
IsValid = classmethod( _IsValid )
class ViewScale :
"""Zoom level of the document; the N argument is a value representing a percentage (the default is 100)."""
def _IsValid( cls, value ) :
return value is None or (0 < value < 101)
IsValid = classmethod( _IsValid )
class ViewZoomKind :
"""An integer (0 to 2) that represents the zoom kind of the document."""
NONE = 0
FullPage = 1
BestFit = 2
def _IsValid( cls, value ) :
return value in [ None, 0, 1, 2 ]
IsValid = classmethod( _IsValid )
class Languages :
NoLanguage = 1024
Albanian = 1052
Arabic = 1025
Bahasa = 1057
BelgianDutch = 2067
BelgianFrench = 2060
BrazilianPortuguese = 1046
Bulgarian = 1026
Catalan = 1027
CroatoSerbianLatin = 1050
Czech = 1029
Danish = 1030
Dutch = 1043
EnglishAustralian = 3081
EnglishUK = 2057
EnglishUS = 1033
Finnish = 1035
French = 1036
FrenchCanadian = 3084
German = 1031
Greek = 1032
Hebrew = 1037
Hungarian = 1038
Icelandic = 1039
Italian = 1040
Japanese = 1041
Korean = 1042
NorwegianBokmal = 1044
NorwegianNynorsk = 2068
Polish = 1045
Portuguese = 2070
RhaetoRomanic = 1047
Romanian = 1048
Russian = 1049
SerboCroatianCyrillic = 2074
SimplifiedChinese = 2052
Slovak = 1051
SpanishCastilian = 1034
SpanishMexican = 2058
Swedish = 1053
SwissFrench = 4108
SwissGerman = 2055
SwissItalian = 2064
Thai = 1054
TraditionalChinese = 1028
Turkish = 1055
Urdu = 1056
SesothoSotho = 1072
Afrikaans = 1078
Zulu = 1077
Xhosa = 1076
Venda = 1075
Tswana = 1074
Tsonga = 1073
FarsiPersian = 1065
Codes = [ 1024,
1052,
1025,
1057,
2067,
2060,
1046,
1026,
1027,
1050,
1029,
1030,
1043,
3081,
2057,
1033,
1035,
1036,
3084,
1031,
1032,
1037,
1038,
1039,
1040,
1041,
1042,
1044,
2068,
1045,
2070,
1047,
1048,
1049,
2074,
2052,
1051,
1034,
2058,
1053,
4108,
2055,
2064,
1054,
1028,
1055,
1056,
1072,
1078,
1077,
1076,
1075,
1074,
1073,
1065 ]
# make it Australian as that is what I use most of the time
DEFAULT = EnglishAustralian
def _IsValid( cls, value ) :
return value in cls.Codes
IsValid = classmethod( _IsValid )
if __name__ == '__main__' :
PrintHexTable()
+757
View File
@@ -0,0 +1,757 @@
from types import IntType, FloatType, LongType, StringTypes
from copy import deepcopy
from binascii import hexlify
from Constants import *
from Styles import *
class UnhandledParamError( Exception ) :
def __init__( self, param ) :
Exception.__init__( self, "Don't know what to do with param %s" % param )
# red green blue
StandardColours = Colours()
StandardColours.append( Colour( 'Black', 0, 0, 0 ) )
StandardColours.append( Colour( 'Blue', 0, 0, 255 ) )
StandardColours.append( Colour( 'Turquoise', 0, 255, 255 ) )
StandardColours.append( Colour( 'Green', 0, 255, 0 ) )
StandardColours.append( Colour( 'Pink', 255, 0, 255 ) )
StandardColours.append( Colour( 'Red', 255, 0, 0 ) )
StandardColours.append( Colour( 'Yellow', 255, 255, 0 ) )
StandardColours.append( Colour( 'White', 255, 255, 255 ) )
StandardColours.append( Colour( 'Blue Dark', 0, 0, 128 ) )
StandardColours.append( Colour( 'Teal', 0, 128, 128 ) )
StandardColours.append( Colour( 'Green Dark', 0, 128, 0 ) )
StandardColours.append( Colour( 'Violet', 128, 0, 128 ) )
StandardColours.append( Colour( 'Red Dark', 128, 0, 0 ) )
StandardColours.append( Colour( 'Yellow Dark', 128, 128, 0 ) )
StandardColours.append( Colour( 'Grey Dark', 128, 128, 128 ) )
StandardColours.append( Colour( 'Grey', 192, 192, 192 ) )
StandardFonts = Fonts()
StandardFonts.append( Font( 'Arial' , 'swiss' , 0, 2, '020b0604020202020204' ) )
StandardFonts.append( Font( 'Arial Black' , 'swiss' , 0, 2, '020b0a04020102020204' ) )
StandardFonts.append( Font( 'Arial Narrow' , 'swiss' , 0, 2, '020b0506020202030204' ) )
StandardFonts.append( Font( 'Bitstream Vera Sans Mono', 'modern', 0, 1, '020b0609030804020204' ) )
StandardFonts.append( Font( 'Bitstream Vera Sans' , 'swiss' , 0, 2, '020b0603030804020204' ) )
StandardFonts.append( Font( 'Bitstream Vera Serif' , 'roman' , 0, 2, '02060603050605020204' ) )
StandardFonts.append( Font( 'Book Antiqua' , 'roman' , 0, 2, '02040602050305030304' ) )
StandardFonts.append( Font( 'Bookman Old Style' , 'roman' , 0, 2, '02050604050505020204' ) )
StandardFonts.append( Font( 'Castellar' , 'roman' , 0, 2, '020a0402060406010301' ) )
StandardFonts.append( Font( 'Century Gothic' , 'swiss' , 0, 2, '020b0502020202020204' ) )
StandardFonts.append( Font( 'Comic Sans MS' , 'script', 0, 2, '030f0702030302020204' ) )
StandardFonts.append( Font( 'Courier New' , 'modern', 0, 1, '02070309020205020404' ) )
StandardFonts.append( Font( 'Franklin Gothic Medium' , 'swiss' , 0, 2, '020b0603020102020204' ) )
StandardFonts.append( Font( 'Garamond' , 'roman' , 0, 2, '02020404030301010803' ) )
StandardFonts.append( Font( 'Georgia' , 'roman' , 0, 2, '02040502050405020303' ) )
StandardFonts.append( Font( 'Haettenschweiler' , 'swiss' , 0, 2, '020b0706040902060204' ) )
StandardFonts.append( Font( 'Impact' , 'swiss' , 0, 2, '020b0806030902050204' ) )
StandardFonts.append( Font( 'Lucida Console' , 'modern', 0, 1, '020b0609040504020204' ) )
StandardFonts.append( Font( 'Lucida Sans Unicode' , 'swiss' , 0, 2, '020b0602030504020204' ) )
StandardFonts.append( Font( 'Microsoft Sans Serif' , 'swiss' , 0, 2, '020b0604020202020204' ) )
StandardFonts.append( Font( 'Monotype Corsiva' , 'script', 0, 2, '03010101010201010101' ) )
StandardFonts.append( Font( 'Palatino Linotype' , 'roman' , 0, 2, '02040502050505030304' ) )
StandardFonts.append( Font( 'Papyrus' , 'script', 0, 2, '03070502060502030205' ) )
StandardFonts.append( Font( 'Sylfaen' , 'roman' , 0, 2, '010a0502050306030303' ) )
StandardFonts.append( Font( 'Symbol' , 'roman' , 2, 2, '05050102010706020507' ) )
StandardFonts.append( Font( 'Tahoma' , 'swiss' , 0, 2, '020b0604030504040204' ) )
StandardFonts.append( Font( 'Times New Roman' , 'roman' , 0, 2, '02020603050405020304' ) )
StandardFonts.append( Font( 'Trebuchet MS' , 'swiss' , 0, 2, '020b0603020202020204' ) )
StandardFonts.append( Font( 'Verdana' , 'swiss' , 0, 2, '020b0604030504040204' ) )
StandardFonts.Castellar.SetAlternate( StandardFonts.Georgia )
"""
Found the following definition at http://www.pbdr.com/vbtips/gen/convtwip.htm
Twips are screen-independent units used to ensure that the placement and
proportion of screen elements in your screen application are the same on all
display systems. A twip is a unit of screen measurement equal to 1/20 of a
printer's point. The conversion between twips and
inches/centimeters/millimeters is as follows:
There are approximately 1440 twips to a inch (the length of a screen item
measuring one inch when printed).
As there are 2.54 centimeters to 1 inch, then there are approximately 567
twips to a centimeter (the length of a screen item measuring one centimeter
when printed).
Or in millimeters, as there are 25.4 millimeters to 1 inch, therefore there
are approximately 56.7 twips to a millimeter (the length of a screen item
measuring one millimeter when printed)."""
# Width default is 12240, Height default is 15840
StandardPaper = Papers()
StandardPaper.append( Paper( 'LETTER' , 1, 'Letter 8 1/2 x 11 in' , 12240, 15840 ) )
StandardPaper.append( Paper( 'LETTERSMALL' , 2, 'Letter Small 8 1/2 x 11 in' , 12240, 15840 ) )
StandardPaper.append( Paper( 'TABLOID' , 3, 'Tabloid 11 x 17 in' , 15840, 24480 ) )
StandardPaper.append( Paper( 'LEDGER' , 4, 'Ledger 17 x 11 in' , 24480, 15840 ) )
StandardPaper.append( Paper( 'LEGAL' , 5, 'Legal 8 1/2 x 14 in' , 12240, 20160 ) )
StandardPaper.append( Paper( 'STATEMENT' , 6, 'Statement 5 1/2 x 8 1/2 in' , 7920, 12240 ) )
StandardPaper.append( Paper( 'EXECUTIVE' , 7, 'Executive 7 1/4 x 10 1/2 in' , 10440, 15120 ) )
StandardPaper.append( Paper( 'A3' , 8, 'A3 297 x 420 mm' , 16838, 23811 ) )
StandardPaper.append( Paper( 'A4' , 9, 'A4 210 x 297 mm' , 11907, 16838 ) )
StandardPaper.append( Paper( 'A4SMALL' , 10, 'A4 Small 210 x 297 mm' , 11907, 16838 ) )
StandardPaper.append( Paper( 'A5' , 11, 'A5 148 x 210 mm' , 8391, 11907 ) )
StandardPaper.append( Paper( 'B4' , 12, 'B4 (JIS) 250 x 354' , 14175, 20072 ) )
StandardPaper.append( Paper( 'B5' , 13, 'B5 (JIS) 182 x 257 mm' , 10319, 14572 ) )
StandardPaper.append( Paper( 'FOLIO' , 14, 'Folio 8 1/2 x 13 in' , 12240, 18720 ) )
StandardPaper.append( Paper( 'QUARTO' , 15, 'Quarto 215 x 275 mm' , 12191, 15593 ) )
StandardPaper.append( Paper( '10X14' , 16, '10x14 in' , 14400, 20160 ) )
StandardPaper.append( Paper( '11X17' , 17, '11x17 in' , 15840, 24480 ) )
StandardPaper.append( Paper( 'NOTE' , 18, 'Note 8 1/2 x 11 in' , 12240, 15840 ) )
StandardPaper.append( Paper( 'ENV_9' , 19, 'Envelope #9 3 7/8 x 8 7/8' , 5580, 12780 ) )
StandardPaper.append( Paper( 'ENV_10' , 20, 'Envelope #10 4 1/8 x 9 1/2' , 5940, 13680 ) )
StandardPaper.append( Paper( 'ENV_11' , 21, 'Envelope #11 4 1/2 x 10 3/8' , 6480, 14940 ) )
StandardPaper.append( Paper( 'ENV_12' , 22, 'Envelope #12 4 3/4 x 11' , 6840, 15840 ) )
StandardPaper.append( Paper( 'ENV_14' , 23, 'Envelope #14 5 x 11 1/2' , 7200, 16560 ) )
StandardPaper.append( Paper( 'CSHEET' , 24, 'C size sheet 18 x 24 in' , 29520, 34560 ) )
StandardPaper.append( Paper( 'DSHEET' , 25, 'D size sheet 22 x 34 in' , 31680, 48960 ) )
StandardPaper.append( Paper( 'ESHEET' , 26, 'E size sheet 34 x 44 in' , 48960, 63360 ) )
StandardPaper.append( Paper( 'ENV_DL' , 27, 'Envelope DL 110 x 220mm' , 6237, 12474 ) )
StandardPaper.append( Paper( 'ENV_C5' , 28, 'Envelope C5 162 x 229 mm' , 9185, 12984 ) )
StandardPaper.append( Paper( 'ENV_C3' , 29, 'Envelope C3 324 x 458 mm' , 18371, 25969 ) )
StandardPaper.append( Paper( 'ENV_C4' , 30, 'Envelope C4 229 x 324 mm' , 12984, 18371 ) )
StandardPaper.append( Paper( 'ENV_C6' , 31, 'Envelope C6 114 x 162 mm' , 6464, 9185 ) )
StandardPaper.append( Paper( 'ENV_C65' , 32, 'Envelope C65 114 x 229 mm' , 6464, 12984 ) )
StandardPaper.append( Paper( 'ENV_B4' , 33, 'Envelope B4 250 x 353 mm' , 14175, 20015 ) )
StandardPaper.append( Paper( 'ENV_B5' , 34, 'Envelope B5 176 x 250 mm' , 9979, 14175 ) )
StandardPaper.append( Paper( 'ENV_B6' , 35, 'Envelope B6 176 x 125 mm' , 9979, 7088 ) )
StandardPaper.append( Paper( 'ENV_ITALY' , 36, 'Envelope 110 x 230 mm' , 6237, 13041 ) )
StandardPaper.append( Paper( 'ENV_MONARCH' , 37, 'Envelope Monarch 3.875 x 7.5 in' , 5580, 10800 ) )
StandardPaper.append( Paper( 'ENV_PERSONAL' , 38, '6 3/4 Envelope 3 5/8 x 6 1/2 in' , 5220, 9360 ) )
StandardPaper.append( Paper( 'FANFOLD_US' , 39, 'US Std Fanfold 14 7/8 x 11 in' , 21420, 15840 ) )
StandardPaper.append( Paper( 'FANFOLD_STD_GERMAN' , 40, 'German Std Fanfold 8 1/2 x 12 in' , 12240, 17280 ) )
StandardPaper.append( Paper( 'FANFOLD_LGL_GERMAN' , 41, 'German Legal Fanfold 8 1/2 x 13 in' , 12240, 18720 ) )
#
# Finally a StyleSheet in which all of this stuff is put together
#
class StyleSheet :
def __init__( self, colours=None, fonts=None ) :
self.Colours = colours or deepcopy( StandardColours )
self.Fonts = fonts or deepcopy( StandardFonts )
self.TextStyles = AttributedList()
self.ParagraphStyles = AttributedList()
class Section( list ) :
NONE = 1
COLUMN = 2
PAGE = 3
EVEN = 4
ODD = 5
BREAK_TYPES = [ NONE, COLUMN, PAGE, EVEN, ODD ]
def __init__( self, paper=None, margins=None, break_type=None, headery=None, footery=None, landscape=None, first_page_number=None ) :
super( Section, self ).__init__()
self.Paper = paper or StandardPaper.A4
self.SetMargins( margins )
self.Header = []
self.Footer = []
self.FirstHeader = []
self.FirstFooter = []
self.SetBreakType( break_type or self.NONE )
self.SetHeaderY( headery )
self.SetFooterY( footery )
self.SetLandscape( landscape )
self.SetFirstPageNumber( first_page_number )
def TwipsToRightMargin( self ) :
return self.Paper.Width - ( self.Margins.Left + self.Margins.Right )
def SetMargins( self, value ) :
self.Margins = value or MarginsPropertySet( top=1000, left=1200, bottom=1000, right=1200 )
self.Width = self.Paper.Width - ( self.Margins.Left + self.Margins.Right )
def SetBreakType( self, value ) :
assert value in self.BREAK_TYPES
self.BreakType = value
return self
def SetHeaderY( self, value ) :
self.HeaderY = value
return self
def SetFooterY( self, value ) :
self.FooterY = value
return self
def SetLandscape( self, value ) :
self.Landscape = False
if value : self.Landscape = True
return self
def SetFirstPageNumber( self, value ) :
self.FirstPageNumber = value
return self
def MakeDefaultStyleSheet( ) :
result = StyleSheet()
NormalText = TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) )
ps = ParagraphStyle( 'Normal',
NormalText.Copy(),
ParagraphPropertySet( space_before = 60,
space_after = 60 ) )
result.ParagraphStyles.append( ps )
ps = ParagraphStyle( 'Normal Short',
NormalText.Copy() )
result.ParagraphStyles.append( ps )
NormalText.TextPropertySet.SetSize( 32 )
ps = ParagraphStyle( 'Heading 1',
NormalText.Copy(),
ParagraphPropertySet( space_before = 240,
space_after = 60 ) )
result.ParagraphStyles.append( ps )
NormalText.TextPropertySet.SetSize( 24 ).SetBold( True )
ps = ParagraphStyle( 'Heading 2',
NormalText.Copy(),
ParagraphPropertySet( space_before = 240,
space_after = 60 ) )
result.ParagraphStyles.append( ps )
# Add some more in that are based on the normal template but that
# have some indenting set that makes them suitable for doing numbered
normal_numbered = result.ParagraphStyles.Normal.Copy()
normal_numbered.SetName( 'Normal Numbered' )
normal_numbered.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 )
normal_numbered.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH )
result.ParagraphStyles.append( normal_numbered )
normal_numbered2 = result.ParagraphStyles.Normal.Copy()
normal_numbered2.SetName( 'Normal Numbered 2' )
normal_numbered2.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 )
normal_numbered2.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH * 2 )
result.ParagraphStyles.append( normal_numbered2 )
## LIST STYLES
for idx, indent in [ (1, TabPS.DEFAULT_WIDTH ),
(2, TabPS.DEFAULT_WIDTH * 2),
(3, TabPS.DEFAULT_WIDTH * 3) ] :
indent = TabPropertySet.DEFAULT_WIDTH
ps = ParagraphStyle( 'List %s' % idx,
TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) ),
ParagraphPropertySet( space_before = 60,
space_after = 60,
first_line_indent = -indent,
left_indent = indent) )
result.ParagraphStyles.append( ps )
return result
class TAB : pass
class LINE : pass
class RawCode :
def __init__( self, data ) :
self.Data = data
PAGE_NUMBER = RawCode( r'{\field{\fldinst page}}' )
TOTAL_PAGES = RawCode( r'{\field{\fldinst numpages}}' )
SECTION_PAGES = RawCode( r'{\field{\fldinst sectionpages}}' )
ARIAL_BULLET = RawCode( r'{\f2\'95}' )
def _get_jpg_dimensions( fin ):
"""
converted from: http://dev.w3.org/cvsweb/Amaya/libjpeg/rdjpgcom.c?rev=1.2
"""
M_SOF0 = chr( 0xC0 ) # /* Start Of Frame N */
M_SOF1 = chr( 0xC1 ) # /* N indicates which compression process */
M_SOF2 = chr( 0xC2 ) # /* Only SOF0-SOF2 are now in common use */
M_SOF3 = chr( 0xC3 ) #
M_SOF5 = chr( 0xC5 ) # /* NB: codes C4 and CC are NOT SOF markers */
M_SOF6 = chr( 0xC6 ) #
M_SOF7 = chr( 0xC7 ) #
M_SOF9 = chr( 0xC9 ) #
M_SOF10 = chr( 0xCA ) #
M_SOF11 = chr( 0xCB ) #
M_SOF13 = chr( 0xCD ) #
M_SOF14 = chr( 0xCE ) #
M_SOF15 = chr( 0xCF ) #
M_SOI = chr( 0xD8 ) # /* Start Of Image (beginning of datastream) */
M_EOI = chr( 0xD9 ) # /* End Of Image (end of datastream) */
M_FF = chr( 0xFF )
MARKERS = [ M_SOF0, M_SOF1, M_SOF2, M_SOF3,
M_SOF5, M_SOF6, M_SOF7, M_SOF9,
M_SOF10,M_SOF11, M_SOF13, M_SOF14,
M_SOF15 ]
def get_length() :
b1 = fin.read( 1 )
b2 = fin.read( 1 )
return (ord(b1) << 8) + ord(b2)
def next_marker() :
# markers come straight after an 0xFF so skip everything
# up to the first 0xFF that we find
while fin.read(1) != M_FF :
pass
# there can be more than one 0xFF as they can be used
# for padding so we are now looking for the first byte
# that isn't an 0xFF, this will be the marker
while True :
result = fin.read(1)
if result != M_FF :
return result
raise Exception( 'Invalid JPEG' )
# BODY OF THE FUNCTION
if not ((fin.read(1) == M_FF) and (fin.read(1) == M_SOI)) :
raise Exception( 'Invalid Jpeg' )
while True :
marker = next_marker()
# the marker is always followed by two bytes representing the length of the data field
length = get_length ()
if length < 2 : raise Exception( "Erroneous JPEG marker length" )
# if it is a compression process marker then it will contain the dimension of the image
if marker in MARKERS :
# the next byte is the data precision, just skip it
fin.read(1)
# bingo
image_height = get_length()
image_width = get_length()
return image_width, image_height
# just skip whatever data it contains
fin.read( length - 2 )
raise Exception( 'Invalid JPEG, end of stream reached' )
_PNG_HEADER = '\x89\x50\x4e'
def _get_png_dimensions( data ) :
if data[0:3] != _PNG_HEADER :
raise Exception( 'Invalid PNG image' )
width = (ord(data[18]) * 256) + (ord(data[19]))
height = (ord(data[22]) * 256) + (ord(data[23]))
return width, height
def _get_emf_dimensions( fin ):
import struct
def get_DWORD():
return struct.unpack("<L",fin.read(4))[0]
def get_LONG():
return struct.unpack("<l",fin.read(4))[0]
def get_WORD():
return struct.unpack("<H",fin.read(2))[0]
class Empty:
pass
header = Empty()
header.RecordType = get_DWORD() # Record type
header.RecordSize = get_DWORD() # Size of the record in bytes
header.BoundsLeft = get_LONG() # Left inclusive bounds
header.BoundsTop = get_LONG() # Top inclusive bounds
header.BoundsRight = get_LONG() # Right inclusive bounds
header.BoundsBottom = get_LONG() # Bottom inclusive bounds
header.FrameLeft = get_LONG() # Left side of inclusive picture frame
header.FrameTop = get_LONG() # Top side of inclusive picture frame
header.FrameRight = get_LONG() # Right side of inclusive picture frame
header.FrameBottom = get_LONG() # Bottom side of inclusive picture frame
header.Signature = get_DWORD() # Signature ID (always 0x464D4520)
header.Version = get_DWORD() # Version of the metafile
header.Size = get_DWORD() # Size of the metafile in bytes
header.NumOfRecords = get_DWORD() # Number of records in the metafile
header.NumOfHandles = get_WORD() # Number of handles in the handle table
header.Reserved = get_WORD() # Not used (always 0)
header.SizeOfDescrip = get_DWORD() # Size of description string in WORDs
header.OffsOfDescrip = get_DWORD() # Offset of description string in metafile
header.NumPalEntries = get_DWORD() # Number of color palette entries
header.WidthDevPixels = get_LONG() # Width of reference device in pixels
header.HeightDevPixels = get_LONG() # Height of reference device in pixels
header.WidthDevMM = get_LONG() # Width of reference device in millimeters
header.HeightDevMM = get_LONG() # Height of reference device in millimeters
if 0:
klist = header.__dict__.keys()
klist.sort()
for k in klist:
print "%20s:%s" % (k,header.__dict__[k])
dw = header.FrameRight-header.FrameLeft
dh = header.FrameBottom-header.FrameTop
# convert from 0.01mm units to 1/72in units
return int(dw * 72.0/2540.0), int(dh * 72.0/2540.0)
class Image( RawCode ) :
# Need to add in the width and height in twips as it crashes
# word xp with these values. Still working out the most
# efficient way of getting these values.
# \picscalex100\picscaley100\piccropl0\piccropr0\piccropt0\piccropb0
# picwgoal900\pichgoal281
PNG_LIB = 'pngblip'
JPG_LIB = 'jpegblip'
EMF_LIB = 'emfblip'
PICT_TYPES = { 'png' : PNG_LIB,
'jpg' : JPG_LIB,
'emf' : EMF_LIB}
def __init__( self, infile, **kwargs ) :
if hasattr( infile, 'read' ):
fin = infile
if 'datatype' not in kwargs.keys():
msg = "If passing in a file object, you must also specify type='xxx' where xxx is one of %s" % self.PICT_TYPES.keys()
raise ValueError,msg
file_name = kwargs.pop('datatype')
else:
fin = file( infile, 'rb' )
file_name = infile
pict_type = self.PICT_TYPES[ file_name[ -3 : ].lower() ]
if pict_type == self.PNG_LIB :
width, height = _get_png_dimensions( fin.read( 100 ) )
elif pict_type == self.JPG_LIB :
width, height = _get_jpg_dimensions( fin )
elif pict_type == self.EMF_LIB :
width, height = _get_emf_dimensions( fin )
# if user specified height or width but not both, then
# scale unspecified dimension to maintain aspect ratio
if ('width' in kwargs) and ('height' not in kwargs):
height = int(height * float(kwargs['width'])/width)
elif ('height' in kwargs) and ('width' not in kwargs):
width = int(width * float(kwargs['height'])/height)
width = kwargs.pop('width',width)
height = kwargs.pop('height', height)
codes = [ pict_type,
'picwgoal%s' % (width * 20),
'pichgoal%s' % (height * 20) ]
# let user specify global scaling
scale = kwargs.pop('scale',100)
for kwarg, code, default in [ ( 'scale_x', 'scalex', scale ),
( 'scale_y', 'scaley', scale ),
( 'crop_left', 'cropl', '0' ),
( 'crop_right', 'cropr', '0' ),
( 'crop_top', 'cropt', '0' ),
( 'crop_bottom', 'cropb', '0' ) ] :
codes.append( 'pic%s%s' % ( code, kwargs.pop( kwarg, default ) ) )
# reset back to the start of the file to get all of it and now
# turn it into hex.
fin.seek( 0, 0 )
image = hexlify( fin.read() )
fin.close()
data = []
for i in range( 0, len( image ), 128 ) :
data.append( image[ i : i + 128 ] )
data = r'{\pict{\%s}%s}' % ( '\\'.join( codes ), '\n'.join( data ) )
RawCode.__init__( self, data )
def ToRawCode( self, var_name ) :
return '%s = RawCode( """%s""" )' % ( var_name, self.Data )
class Text :
def __init__( self, *params ) :
self.Data = None
self.Style = None
self.Properties = None
self.Shading = None
for param in params :
if isinstance( param, TextStyle ) : self.Style = param
elif isinstance( param, TextPS ) : self.Properties = param
elif isinstance( param, ShadingPS ) : self.Shading = param
else :
# otherwise let the rendering custom handler sort it out itself
self.Data = param
def SetData( self, value ) :
self.Data = value
class Inline( list ) :
def __init__( self, *params ) :
super( Inline, self ).__init__()
self.Style = None
self.Properties = None
self.Shading = None
self._append = super( Inline, self ).append
for param in params :
if isinstance( param, TextStyle ) : self.Style = param
elif isinstance( param, TextPS ) : self.Properties = param
elif isinstance( param, ShadingPS ) : self.Shading = param
else :
# otherwise we add to it to our list of elements and let
# the rendering custom handler sort it out itself.
self.append( param )
def append( self, *params ) :
# filter out any that are explicitly None
[ self._append( param ) for param in params if param is not None ]
class Paragraph( list ) :
def __init__( self, *params ) :
super( Paragraph, self ).__init__()
self.Style = None
self.Properties = None
self.Frame = None
self.Shading = None
self._append = super( Paragraph, self ).append
for param in params :
if isinstance( param, ParagraphStyle ) : self.Style = param
elif isinstance( param, ParagraphPS ) : self.Properties = param
elif isinstance( param, FramePS ) : self.Frame = param
elif isinstance( param, ShadingPS ) : self.Shading = param
else :
# otherwise we add to it to our list of elements and let
# the rendering custom handler sort it out itself.
self.append( param )
def append( self, *params ) :
# filter out any that are explicitly None
[ self._append( param ) for param in params if param is not None ]
def insert( self, index, value ) :
if value is not None :
super( Paragraph, self ).insert( index, value )
class Table :
LEFT = 1
RIGHT = 2
CENTER = 3
ALIGNMENT = [ LEFT, RIGHT, CENTER ]
NO_WRAPPING = 1
WRAP_AROUND = 2
WRAPPING = [ NO_WRAPPING, WRAP_AROUND ]
# trrh height of row, 0 means automatically adjust, use negative for an absolute
# trgaph is half of the space between a table cell in width, reduce this one
# to get a really tiny column
def __init__( self, *column_widths, **kwargs ) :
self.Rows = []
self.SetAlignment ( kwargs.pop( 'alignment', self.LEFT ) )
self.SetLeftOffset ( kwargs.pop( 'left_offset', None ) )
self.SetGapBetweenCells( kwargs.pop( 'gap_between_cells', None ) )
self.SetColumnWidths ( *column_widths )
assert not kwargs, 'invalid keyword args %s' % kwargs
def SetAlignment( self, value ) :
assert value is None or value in self.ALIGNMENT
self.Alignment = value or self.LEFT
return self
def SetLeftOffset( self, value ) :
self.LeftOffset = value
return self
def SetGapBetweenCells( self, value ) :
self.GapBetweenCells = value
return self
def SetColumnWidths( self, *column_widths ) :
self.ColumnWidths = column_widths
self.ColumnCount = len( column_widths )
return self
def AddRow( self, *cells ) :
height = None
if isinstance( cells[ 0 ], (IntType, FloatType, LongType) ):
height = int( cells[ 0 ] )
cells = cells[ 1 : ]
# make sure all of the spans add up to the number of columns
# otherwise the table will get corrupted
if self.ColumnCount != sum( [ cell.Span for cell in cells ] ) :
raise Exception( 'ColumnCount != the total of this row\'s cell.Spans.' )
self.Rows.append( ( height, cells ) )
append = AddRow
class Cell( list ) :
"""
\clvertalt Text is top-aligned in cell (the default).
\clvertalc Text is centered vertically in cell.
\clvertalb Text is bottom-aligned in cell.
\cltxlrtb Vertical text aligned left (direction bottom up).
\cltxtbrl Vertical text aligned right (direction top down).
"""
ALIGN_TOP = 1
ALIGN_CENTER = 2
ALIGN_BOTTOM = 3
FLOW_LR_TB = 1
FLOW_RL_TB = 2
FLOW_LR_BT = 3
FLOW_VERTICAL_LR_TB = 4
FLOW_VERTICAL_TB_RL = 5
def __init__( self, *params, **kwargs ) :
super( Cell, self ).__init__()
self.SetFrame ( None )
self.SetMargins( None )
self.SetAlignment( kwargs.get( 'alignment', self.ALIGN_TOP ) )
self.SetFlow ( kwargs.get( 'flow' , self.FLOW_LR_TB ) )
self.SetSpan ( kwargs.get( 'span', 1 ) )
self.SetStartVerticalMerge( kwargs.get( 'start_vertical_merge', False ) )
self.SetVerticalMerge ( kwargs.get( 'vertical_merge', False ) )
self._append = super( Cell, self ).append
for param in params :
if isinstance( param, StringType ) : self.append ( param )
elif isinstance( param, Paragraph ) : self.append ( param )
elif isinstance( param, FramePS ) : self.SetFrame ( param )
elif isinstance( param, MarginsPS ) : self.SetMargins( param )
def SetFrame( self, value ) :
self.Frame = value
return self
def SetMargins( self, value ) :
self.Margins = value
return self
def SetAlignment( self, value ) :
assert value in [ self.ALIGN_TOP, self.ALIGN_CENTER, self.ALIGN_BOTTOM ] #, self.ALIGN_TEXT_TOP_DOWN, self.ALIGN_TEXT_BOTTOM_UP ]
self.Alignment = value
def SetFlow( self, value ) :
assert value in [ self.FLOW_LR_TB, self.FLOW_RL_TB, self.FLOW_LR_BT, self.FLOW_VERTICAL_LR_TB, self.FLOW_VERTICAL_TB_RL ]
self.Flow = value
def SetSpan( self, value ) :
# must be a positive integer
self.Span = int( max( value, 1 ) )
return self
def SetStartVerticalMerge( self, value ) :
self.StartVerticalMerge = False
if value :
self.StartVerticalMerge = True
return self
def SetVerticalMerge( self, value ) :
self.VerticalMerge = False
if value :
self.VerticalMerge = True
return self
def append( self, *params ) :
[ self._append( param ) for param in params ]
class Document :
def __init__( self, style_sheet=None, default_language=None, view_kind=None, view_zoom_kind=None, view_scale=None ) :
self.StyleSheet = style_sheet or MakeDefaultStyleSheet()
self.Sections = AttributedList( Section )
self.SetTitle( None )
self.DefaultLanguage = default_language or Languages.DEFAULT
self.ViewKind = view_kind or ViewKind.DEFAULT
self.ViewZoomKind = view_zoom_kind
self.ViewScale = view_scale
def NewSection( self, *params, **kwargs ) :
result = Section( *params, **kwargs )
self.Sections.append( result )
return result
def SetTitle( self, value ) :
self.Title = value
return self
def Copy( self ) :
result = Document( style_sheet = self.StyleSheet.Copy(),
default_language = self.DefaultLanguage,
view_kind = self.ViewKind,
view_zoom_kind = self.ViewZoomKind,
view_scale = self.ViewScale )
result.SetTitle( self.Title )
result.Sections = self.Sections.Copy()
return result
def TEXT( *params, **kwargs ) :
text_props = TextPropertySet()
text_props.SetFont ( kwargs.get( 'font', None ) )
text_props.SetSize ( kwargs.get( 'size', None ) )
text_props.SetBold ( kwargs.get( 'bold', False ) )
text_props.SetItalic ( kwargs.get( 'italic', False ) )
text_props.SetUnderline( kwargs.get( 'underline', False ) )
text_props.SetColour ( kwargs.get( 'colour', None ) )
if len( params ) == 1 :
return Text( params[ 0 ], text_props )
result = Inline( text_props )
apply( result.append, params )
return result
def B( *params ) :
text_props = TextPropertySet( bold=True )
if len( params ) == 1 :
return Text( params[ 0 ], text_props )
result = Inline( text_props )
apply( result.append, params )
return result
def I( *params ) :
text_props = TextPropertySet( italic=True )
if len( params ) == 1 :
return Text( params[ 0 ], text_props )
result = Inline( text_props )
apply( result.append, params )
return result
def U( *params ) :
text_props = TextPropertySet( underline=True )
if len( params ) == 1 :
return Text( params[ 0 ], text_props )
result = Inline( text_props )
apply( result.append, params )
return result
+489
View File
@@ -0,0 +1,489 @@
"""
PropertySets group common attributes together, each property set is used to control a specific part of the rendering.
PropertySets can be used in different elements of the document.
For example the FramePropertySet is used in paragraphs, tables, cells, etc.
The TextPropertySet can be used for text or in a Paragraph Style.
"""
from types import StringType
from copy import deepcopy
#
# We need some basic Type like fonts, colours and paper definitions
#
def MakeAttributeName( value ) :
assert value and type( value ) is StringType
value = value.replace( ' ', '' )
return value
class AttributedList( list ) :
def __init__( self, accepted_type=None ) :
super( AttributedList, self ).__init__()
self.AcceptedType = accepted_type
self._append = super( AttributedList, self ).append
def append( self, *values ) :
for value in values :
if self.AcceptedType : assert isinstance( value, self.AcceptedType )
self._append( value )
name = getattr( value, 'Name', None )
if name :
name = MakeAttributeName( value.Name )
setattr( self, name, value )
def __deepcopy__( self, memo ) :
result = self.__class__()
result.append( *self[:] )
return result
class Colour :
def __init__( self, name, red, green, blue ) :
self.SetName ( name )
self.SetRed ( red )
self.SetGreen( green )
self.SetBlue ( blue )
def SetName( self, value ) :
self.Name = value
return self
def SetRed( self, value ) :
self.Red = value
return self
def SetGreen( self, value ) :
self.Green = value
return self
def SetBlue( self, value ) :
self.Blue = value
return self
class Colours( AttributedList ) :
def __init__( self ) :
super( Colours, self ).__init__( Colour )
class Font :
def __init__( self, name, family, character_set = 0, pitch = None, panose = None, alternate = None ) :
self.SetName ( name )
self.SetFamily ( family )
self.SetCharacterSet( character_set )
self.SetPitch ( pitch )
self.SetPanose ( panose )
self.SetAlternate ( alternate )
def SetName( self, value ) :
self.Name = value
return self
def SetFamily( self, value ) :
self.Family = value
return self
def SetCharacterSet( self, value ) :
self.CharacterSet = value
return self
def SetPitch( self, value ) :
self.Pitch = value
return self
def SetPanose( self, value ) :
self.Panose = value
return self
def SetAlternate( self, value ) :
self.Alternate = value
return self
class Fonts( AttributedList ) :
def __init__( self ) :
super( Fonts, self ).__init__( Font )
class Paper :
def __init__( self, name, code, description, width, height ) :
self.SetName ( name )
self.SetCode ( code )
self.SetDescription( description )
self.SetWidth ( width )
self.SetHeight ( height )
def SetName( self, value ) :
self.Name = value
return self
def SetCode( self, value ) :
self.Code = value
return self
def SetDescription( self, value ) :
self.Description = value
return self
def SetWidth( self, value ) :
self.Width = value
return self
def SetHeight( self, value ) :
self.Height = value
return self
class Papers( AttributedList ) :
def __init__( self ) :
super( Papers, self ).__init__( Paper )
#
# Then we have property sets which represent different aspects of Styles
#
class MarginsPropertySet :
def __init__( self, top=None, left=None, bottom=None, right=None ) :
self.SetTop ( top )
self.SetLeft ( left )
self.SetBottom( bottom )
self.SetRight ( right )
def SetTop( self, value ) :
self.Top = value
return self
def SetLeft( self, value ) :
self.Left = value
return self
def SetBottom( self, value ) :
self.Bottom = value
return self
def SetRight( self, value ) :
self.Right = value
return self
class ShadingPropertySet :
HORIZONTAL = 1
VERTICAL = 2
FORWARD_DIAGONAL = 3
BACKWARD_DIAGONAL = 4
VERTICAL_CROSS = 5
DIAGONAL_CROSS = 6
DARK_HORIZONTAL = 7
DARK_VERTICAL = 8
DARK_FORWARD_DIAGONAL = 9
DARK_BACKWARD_DIAGONAL = 10
DARK_VERTICAL_CROSS = 11
DARK_DIAGONAL_CROSS = 12
PATTERNS = [ HORIZONTAL,
VERTICAL,
FORWARD_DIAGONAL,
BACKWARD_DIAGONAL,
VERTICAL_CROSS,
DIAGONAL_CROSS,
DARK_HORIZONTAL,
DARK_VERTICAL,
DARK_FORWARD_DIAGONAL,
DARK_BACKWARD_DIAGONAL,
DARK_VERTICAL_CROSS,
DARK_DIAGONAL_CROSS ]
def __init__( self, shading=None, pattern=None, foreground=None, background=None ) :
self.SetShading ( shading )
self.SetForeground( foreground )
self.SetBackground( background )
self.SetPattern ( pattern )
def __deepcopy__( self, memo ) :
return ShadingPropertySet( self.Shading,
self.Foreground,
self.Background,
self.Pattern )
def SetShading( self, value ) :
self.Shading = value
return self
def SetPattern( self, value ) :
assert value is None or value in self.PATTERNS
self.Pattern = value
return self
def SetForeground( self, value ) :
assert not value or isinstance( value, Colour )
self.Foreground = value
return self
def SetBackground( self, value ) :
assert not value or isinstance( value, Colour )
self.Background = value
return self
class BorderPropertySet :
SINGLE = 1
DOUBLE = 2
SHADOWED = 3
DOUBLED = 4
DOTTED = 5
DASHED = 6
HAIRLINE = 7
STYLES = [ SINGLE, DOUBLE, SHADOWED, DOUBLED, DOTTED, DASHED, HAIRLINE ]
def __init__( self, width=None, style=None, colour=None, spacing=None ) :
self.SetWidth ( width )
self.SetStyle ( style or self.SINGLE )
self.SetColour ( colour )
self.SetSpacing( spacing )
def SetWidth( self, value ) :
self.Width = value
return self
def SetStyle( self, value ) :
assert value is None or value in self.STYLES
self.Style = value
return self
def SetColour( self, value ) :
assert value is None or isinstance( value, Colour )
self.Colour = value
return self
def SetSpacing( self, value ) :
self.Spacing = value
return self
class FramePropertySet :
def __init__( self, top=None, left=None, bottom=None, right=None ) :
self.SetTop ( top )
self.SetLeft ( left )
self.SetBottom( bottom )
self.SetRight ( right )
def SetTop( self, value ) :
assert value is None or isinstance( value, BorderPropertySet )
self.Top = value
return self
def SetLeft( self, value ) :
assert value is None or isinstance( value, BorderPropertySet )
self.Left = value
return self
def SetBottom( self, value ) :
assert value is None or isinstance( value, BorderPropertySet )
self.Bottom = value
return self
def SetRight( self, value ) :
assert value is None or isinstance( value, BorderPropertySet )
self.Right = value
return self
class TabPropertySet :
DEFAULT_WIDTH = 720
LEFT = 1
RIGHT = 2
CENTER = 3
DECIMAL = 4
ALIGNMENT = [ LEFT, RIGHT, CENTER, DECIMAL ]
DOTS = 1
HYPHENS = 2
UNDERLINE = 3
THICK_LINE = 4
EQUAL_SIGN = 5
LEADERS = [ DOTS, HYPHENS, UNDERLINE, THICK_LINE, EQUAL_SIGN ]
def __init__( self, width=None, alignment=None, leader=None ) :
self.SetWidth ( width )
self.SetAlignment( alignment or self.LEFT )
self.SetLeader ( leader )
def SetWidth( self, value ) :
self.Width = value
return self
def SetAlignment( self, value ) :
assert value in self.ALIGNMENT
self.Alignment = value
return self
def SetLeader( self, value ) :
assert not value or value in self.LEADERS
self.Leader = value
return self
class TextPropertySet :
def __init__( self, font=None, size=None, bold=None, italic=None, underline=None, colour=None, frame=None, expansion=None ) :
self.SetFont ( font )
self.SetSize ( size )
self.SetBold ( bold or False )
self.SetItalic ( italic or False )
self.SetUnderline ( underline or False )
self.SetColour( colour )
self.SetFrame ( frame )
self.SetStrikeThrough ( False )
self.SetDottedUnderline( False )
self.SetDoubleUnderline( False )
self.SetWordUnderline ( False )
self.SetExpansion ( expansion )
def Copy( self ) :
return deepcopy( self )
def __deepcopy__( self, memo ) :
# the font must remain a reference to the same font that we are looking at
# so we want to stop the recursiveness at this point and return an object
# with the right references.
result = TextPropertySet( self.Font,
self.Size,
self.Bold,
self.Italic,
self.Underline,
self.Colour,
deepcopy( self.Frame, memo ) )
result.SetStrikeThrough( self.StrikeThrough )
return result
def SetFont( self, value ) :
assert not value or isinstance( value, Font )
self.Font = value
return self
def SetSize( self, value ) :
self.Size = value
return self
def SetBold( self, value ) :
self.Bold = False
if value : self.Bold = True
return self
def SetItalic( self, value ) :
self.Italic = False
if value : self.Italic = True
return self
def SetUnderline( self, value ) :
self.Underline = False
if value : self.Underline = True
return self
def SetColour( self, value ) :
assert value is None or isinstance( value, Colour )
self.Colour = value
return self
def SetFrame( self, value ) :
assert value is None or isinstance( value, BorderPropertySet )
self.Frame = value
return self
def SetStrikeThrough( self, value ) :
self.StrikeThrough = False
if value : self.StrikeThrough = True
return self
def SetDottedUnderline( self, value ) :
self.DottedUnderline = False
if value : self.DottedUnderline = True
return self
def SetDoubleUnderline( self, value ) :
self.DoubleUnderline = False
if value : self.DoubleUnderline = True
return self
def SetWordUnderline( self, value ) :
self.WordUnderline = False
if value : self.WordUnderline = True
return self
def SetExpansion( self, value ) :
self.Expansion = value
return self
class ParagraphPropertySet :
LEFT = 1
RIGHT = 2
CENTER = 3
JUSTIFY = 4
DISTRIBUTE = 5
ALIGNMENT = [ LEFT, RIGHT, CENTER, JUSTIFY, DISTRIBUTE ]
def __init__( self, alignment=None, space_before=None, space_after=None, tabs=None, first_line_indent=None, left_indent=None, right_indent=None, page_break_before=None ) :
self.SetAlignment ( alignment or self.LEFT )
self.SetSpaceBefore( space_before )
self.SetSpaceAfter ( space_after )
self.Tabs = []
if tabs : apply( self.SetTabs, tabs )
self.SetFirstLineIndent( first_line_indent or None )
self.SetLeftIndent ( left_indent or None )
self.SetRightIndent ( right_indent or None )
self.SetPageBreakBefore( page_break_before )
self.SetSpaceBetweenLines( None )
def Copy( self ) :
return deepcopy( self )
def SetAlignment( self, value ) :
assert not value or value in self.ALIGNMENT
self.Alignment = value or self.LEFT
return self
def SetSpaceBefore( self, value ) :
self.SpaceBefore = value
return self
def SetSpaceAfter( self, value ) :
self.SpaceAfter = value
return self
def SetTabs( self, *params ) :
self.Tabs = params
return self
def SetFirstLineIndent( self, value ) :
self.FirstLineIndent = value
return self
def SetLeftIndent( self, value ) :
self.LeftIndent = value
return self
def SetRightIndent( self, value ) :
self.RightIndent = value
return self
def SetSpaceBetweenLines( self, value ) :
self.SpaceBetweenLines = value
return self
def SetPageBreakBefore( self, value ) :
self.PageBreakBefore = False
if value : self.PageBreakBefore = True
return self
# Some short cuts to make the code a bit easier to read
MarginsPS = MarginsPropertySet
ShadingPS = ShadingPropertySet
BorderPS = BorderPropertySet
FramePS = FramePropertySet
TabPS = TabPropertySet
TextPS = TextPropertySet
ParagraphPS = ParagraphPropertySet
+19
View File
@@ -0,0 +1,19 @@
Version 0.46
Added EMF support.
Added more sophisticated scaling options.
See examples2.py for both.
Grant Edwards, grante@users.sourceforge.net
Version 0.45
Finally, image support!!! Handles PNGs and JPGs.
See examples2.py for the gory details.
Simon Cusack, scusack@sourceforge.net
+639
View File
@@ -0,0 +1,639 @@
from types import StringType, ListType, TupleType
from copy import deepcopy
from Elements import *
DEFAULT_TAB_WIDTH = 720
ParagraphAlignmentMap = { ParagraphPropertySet.LEFT : 'ql',
ParagraphPropertySet.RIGHT : 'qr',
ParagraphPropertySet.CENTER : 'qc',
ParagraphPropertySet.JUSTIFY : 'qj',
ParagraphPropertySet.DISTRIBUTE : 'qd' }
TabAlignmentMap = { TabPropertySet.LEFT : '',
TabPropertySet.RIGHT : 'tqr',
TabPropertySet.CENTER : 'tqc',
TabPropertySet.DECIMAL : 'tqdec' }
TableAlignmentMap = { Table.LEFT : 'trql',
Table.RIGHT : 'trqr',
Table.CENTER : 'trqc' }
CellAlignmentMap = { Cell.ALIGN_TOP : '', # clvertalt
Cell.ALIGN_CENTER : 'clvertalc',
Cell.ALIGN_BOTTOM : 'clvertalb' }
CellFlowMap = { Cell.FLOW_LR_TB : '', # cltxlrtb, Text in a cell flows from left to right and top to bottom (default)
Cell.FLOW_RL_TB : 'cltxtbrl', # Text in a cell flows right to left and top to bottom
Cell.FLOW_LR_BT : 'cltxbtlr', # Text in a cell flows left to right and bottom to top
Cell.FLOW_VERTICAL_LR_TB : 'cltxlrtbv', # Text in a cell flows left to right and top to bottom, vertical
Cell.FLOW_VERTICAL_TB_RL : 'cltxtbrlv' } # Text in a cell flows top to bottom and right to left, vertical
ShadingPatternMap = { ShadingPropertySet.HORIZONTAL : 'bghoriz',
ShadingPropertySet.VERTICAL : 'bgvert',
ShadingPropertySet.FORWARD_DIAGONAL : 'bgfdiag',
ShadingPropertySet.BACKWARD_DIAGONAL : 'bgbdiag',
ShadingPropertySet.VERTICAL_CROSS : 'bgcross',
ShadingPropertySet.DIAGONAL_CROSS : 'bgdcross',
ShadingPropertySet.DARK_HORIZONTAL : 'bgdkhoriz',
ShadingPropertySet.DARK_VERTICAL : 'bgdkvert',
ShadingPropertySet.DARK_FORWARD_DIAGONAL : 'bgdkfdiag',
ShadingPropertySet.DARK_BACKWARD_DIAGONAL : 'bgdkbdiag',
ShadingPropertySet.DARK_VERTICAL_CROSS : 'bgdkcross',
ShadingPropertySet.DARK_DIAGONAL_CROSS : 'bgdkdcross' }
TabLeaderMap = { TabPropertySet.DOTS : 'tldot',
TabPropertySet.HYPHENS : 'tlhyph',
TabPropertySet.UNDERLINE : 'tlul',
TabPropertySet.THICK_LINE : 'tlth',
TabPropertySet.EQUAL_SIGN : 'tleq' }
BorderStyleMap = { BorderPropertySet.SINGLE : 'brdrs',
BorderPropertySet.DOUBLE : 'brdrth',
BorderPropertySet.SHADOWED : 'brdrsh',
BorderPropertySet.DOUBLED : 'brdrdb',
BorderPropertySet.DOTTED : 'brdrdot',
BorderPropertySet.DASHED : 'brdrdash',
BorderPropertySet.HAIRLINE : 'brdrhair' }
SectionBreakTypeMap = { Section.NONE : 'sbknone',
Section.COLUMN : 'sbkcol',
Section.PAGE : 'sbkpage',
Section.EVEN : 'sbkeven',
Section.ODD : 'sbkodd' }
class Settings( list ) :
def __init__( self ) :
super( Settings, self ).__init__()
self._append = super( Settings, self ).append
def append( self, value, mask=None, fallback=None ) :
if (value is not 0) and value in [ False, None, '' ] :
if fallback : self._append( self, fallback )
else :
if mask :
if value is True :
value = mask
else :
value = mask % value
self._append( value )
def Join( self ) :
if self : return r'\%s' % '\\'.join( self )
return ''
def __repr__( self ) :
return self.Join()
class Renderer :
def __init__( self, write_custom_element_callback=None ) :
self.character_style_map = {}
self.paragraph_style_map = {}
self.WriteCustomElement = write_custom_element_callback
#
# All of the Rend* Functions populate a Settings object with values
#
def _RendPageProperties( self, section, settings, in_section ) :
# this one is different from the others as it takes the settings from a
if in_section :
#paper_size_code = 'psz%s'
paper_width_code = 'pgwsxn%s'
paper_height_code = 'pghsxn%s'
landscape = 'lndscpsxn'
margin_suffix = 'sxn'
else :
#paper_size_code = 'psz%s'
paper_width_code = 'paperw%s'
paper_height_code = 'paperh%s'
landscape = 'landscape'
margin_suffix = ''
#settings.append( section.Paper.Code, paper_size_code )
settings.append( section.Paper.Width, paper_width_code )
settings.append( section.Paper.Height, paper_height_code )
if section.Landscape :
settings.append( landscape )
if section.FirstPageNumber :
settings.append( section.FirstPageNumber, 'pgnstarts%s' )
settings.append( 'pgnrestart' )
self._RendMarginsPropertySet( section.Margins, settings, margin_suffix )
def _RendShadingPropertySet( self, shading_props, settings, prefix='' ) :
if not shading_props : return
settings.append( shading_props.Shading, prefix + 'shading%s' )
settings.append( ShadingPatternMap.get( shading_props.Pattern, False ) )
settings.append( self._colour_map.get( shading_props.Foreground, False ), prefix + 'cfpat%s' )
settings.append( self._colour_map.get( shading_props.Background, False ), prefix + 'cbpat%s' )
def _RendBorderPropertySet( self, edge_props, settings ) :
settings.append( BorderStyleMap[ edge_props.Style ] )
settings.append( edge_props.Width , 'brdrw%s' )
settings.append( self._colour_map.get( edge_props.Colour, False ), 'brdrcf%s' )
settings.append( edge_props.Spacing or False , 'brsp%s' )
def _RendFramePropertySet( self, frame_props, settings, tag_prefix='' ) :
if not frame_props : return
if frame_props.Top :
settings.append( tag_prefix + 'brdrt' )
self._RendBorderPropertySet( frame_props.Top, settings )
if frame_props.Left :
settings.append( tag_prefix + 'brdrl' )
self._RendBorderPropertySet( frame_props.Left, settings )
if frame_props.Bottom :
settings.append( tag_prefix + 'brdrb' )
self._RendBorderPropertySet( frame_props.Bottom, settings )
if frame_props.Right :
settings.append( tag_prefix + 'brdrr' )
self._RendBorderPropertySet( frame_props.Right, settings )
def _RendMarginsPropertySet( self, margin_props, settings, suffix='' ) :
if not margin_props : return
settings.append( margin_props.Top, 'margt' + suffix + '%s' )
settings.append( margin_props.Left, 'margl' + suffix + '%s' )
settings.append( margin_props.Bottom, 'margb' + suffix + '%s' )
settings.append( margin_props.Right, 'margr' + suffix + '%s' )
def _RendParagraphPropertySet( self, paragraph_props, settings ) :
if not paragraph_props : return
settings.append( ParagraphAlignmentMap[ paragraph_props.Alignment ] )
settings.append( paragraph_props.SpaceBefore, 'sb%s' )
settings.append( paragraph_props.SpaceAfter, 'sa%s' )
# then we have to find out all of the tabs
width = 0
for tab in paragraph_props.Tabs :
settings.append( TabAlignmentMap[ tab.Alignment ] )
settings.append( TabLeaderMap.get( tab.Leader, '' ) )
width += tab.Width or DEFAULT_TAB_WIDTH
settings.append( 'tx%s' % width )
settings.append( paragraph_props.PageBreakBefore, 'pagebb' )
settings.append( paragraph_props.FirstLineIndent, 'fi%s' )
settings.append( paragraph_props.LeftIndent, 'li%s' )
settings.append( paragraph_props.RightIndent, 'ri%s' )
if paragraph_props.SpaceBetweenLines :
if paragraph_props.SpaceBetweenLines < 0 :
settings.append( paragraph_props.SpaceBetweenLines, r'sl%s\slmult0' )
else :
settings.append( paragraph_props.SpaceBetweenLines, r'sl%s\slmult1' )
def _RendTextPropertySet( self, text_props, settings ) :
if not text_props : return
if text_props.Expansion :
settings.append( text_props.Expansion, 'expndtw%s' )
settings.append( text_props.Bold, 'b' )
settings.append( text_props.Italic, 'i' )
settings.append( text_props.Underline, 'ul' )
settings.append( text_props.DottedUnderline, 'uld' )
settings.append( text_props.DoubleUnderline, 'uldb' )
settings.append( text_props.WordUnderline, 'ulw' )
settings.append( self._font_map.get( text_props.Font, False ), 'f%s' )
settings.append( text_props.Size, 'fs%s' )
settings.append( self._colour_map.get( text_props.Colour, False ), 'cf%s' )
if text_props.Frame :
frame = text_props.Frame
settings.append( 'chbrdr' )
settings.append( BorderStyleMap[ frame.Style ] )
settings.append( frame.Width , 'brdrw%s' )
settings.append( self._colour_map.get( frame.Colour, False ), 'brdrcf%s' )
#
# All of the Write* functions will write to the internal file object
#
# the _ ones probably don't need to be used by anybody outside
# but the other ones like WriteTextElement could be used in the Custom
# callback.
def Write( self, document, fout ) :
# write all of the standard stuff based upon the first document
self._doc = document
self._fout = fout
self._WriteDocument ()
self._WriteColours ()
self._WriteFonts ()
self._WriteStyleSheet()
settings = Settings()
self._RendPageProperties( self._doc.Sections[ 0 ], settings, in_section=False )
self._write( repr( settings ) )
# handle the simplest case first, we don't need to do anymore mucking around
# with section headers, etc we can just rip the document out
if len( document.Sections ) == 1 :
self._WriteSection( document.Sections[ 0 ],
is_first = True,
add_header = False )
else :
for section_idx, section in enumerate( document.Sections ) :
is_first = section_idx == 0
add_header = True
self._WriteSection( section, is_first, add_header )
self._write( '}' )
del self._fout, self._doc, self._CurrentStyle
def _write( self, data, *params ) :
#----------------------------------
# begin modification
# by Herbert Weinhandl
# to convert accented characters
# to their rtf-compatible form
#for c in range( 128, 256 ) :
# data = data.replace( chr(c), "\'%x" % c)
# end modification
#
# This isn't the right place for this as it is going to do
# this loop for all sorts of writes, including settings, control codes, etc.
#
# I will create a def _WriteText (or something) method that is used when the
# actual string that is to be viewed in the document is written, this can then
# do the final accented character check.
#
# I left it here so that I remember to do the right thing when I have time
#----------------------------------
if params : data = data % params
self._fout.write( data )
def _WriteDocument( self ) :
settings = Settings()
assert Languages.IsValid ( self._doc.DefaultLanguage )
assert ViewKind.IsValid ( self._doc.ViewKind )
assert ViewZoomKind.IsValid( self._doc.ViewZoomKind )
assert ViewScale.IsValid ( self._doc.ViewScale )
settings.append( self._doc.DefaultLanguage, 'deflang%s' )
settings.append( self._doc.ViewKind , 'viewkind%s' )
settings.append( self._doc.ViewZoomKind , 'viewzk%s' )
settings.append( self._doc.ViewScale , 'viewscale%s' )
self._write( "{\\rtf1\\ansi\\ansicpg1252\\deff0%s\n" % settings )
def _WriteColours( self ) :
self._write( r"{\colortbl ;" )
self._colour_map = {}
offset = 0
for colour in self._doc.StyleSheet.Colours :
self._write( r'\red%s\green%s\blue%s;', colour.Red, colour.Green, colour.Blue )
self._colour_map[ colour ] = offset + 1
offset += 1
self._write( "}\n" )
def _WriteFonts( self ) :
self._write( r'{\fonttbl' )
self._font_map = {}
offset = 0
for font in self._doc.StyleSheet.Fonts :
pitch = ''
panose = ''
alternate = ''
if font.Pitch : pitch = r'\fprq%s' % font.Pitch
if font.Panose : panose = r'{\*\panose %s}' % font.Panose
if font.Alternate : alternate = r'{\*\falt %s}' % font.Alternate.Name
self._write( r'{\f%s\f%s%s\fcharset%s%s %s%s;}',
offset,
font.Family,
pitch,
font.CharacterSet,
panose,
font.Name,
alternate )
self._font_map[ font ] = offset
offset += 1
self._write( "}\n" )
def _WriteStyleSheet( self ) :
self._write( r"{\stylesheet" )
# TO DO: character styles, does anybody actually use them?
offset_map = {}
for idx, style in enumerate( self._doc.StyleSheet.ParagraphStyles ) :
offset_map[ style ] = idx
# paragraph styles
self.paragraph_style_map = {}
for idx, style in enumerate( self._doc.StyleSheet.ParagraphStyles ) :
if idx == 0 :
default = style
else :
self._write( '\n' )
settings = Settings()
# paragraph properties
self._RendParagraphPropertySet( style.ParagraphPropertySet, settings )
self._RendFramePropertySet ( style.FramePropertySet, settings )
self._RendShadingPropertySet ( style.ShadingPropertySet, settings )
# text properties
self._RendTextPropertySet ( style.TextStyle.TextPropertySet, settings )
self._RendShadingPropertySet( style.TextStyle.ShadingPropertySet, settings )
# have to take
based_on = '\\sbasedon%s' % offset_map.get( style.BasedOn, 0 )
next = '\\snext%s' % offset_map.get( style.Next, 0 )
inln = '\\s%s%s' % ( idx, settings )
self._write( "{%s%s%s %s;}", inln, based_on, next, style.Name )
self.paragraph_style_map[ style ] = inln
# if now style is specified for the first paragraph to be written, this one
# will be used
self._CurrentStyle = self.paragraph_style_map[ default ]
self._write( "}\n" )
def _WriteSection( self, section, is_first, add_header ) :
def WriteHF( hf, rtfword ) :
#if not hf : return
# if we don't have anything in the header/footer then include
# a blank paragraph, this stops it from picking up the header/footer
# from the previous section
# if not hf : hf = [ Paragraph( '' ) ]
if not hf : hf = []
self._write( '{\\%s' % rtfword )
self._WriteElements( hf )
self._write( '}\n' )
settings = Settings()
if not is_first :
# we need to finish off the preceding section
# and reset all of our defaults back to standard
settings.append( 'sect' )
# reset to our defaults
settings.append( 'sectd' )
if add_header :
settings.append( SectionBreakTypeMap[ section.BreakType ] )
self._RendPageProperties( section, settings, in_section=True )
settings.append( section.HeaderY, 'headery%s' )
settings.append( section.FooterY, 'footery%s' )
# write all of these out now as we need to do a write elements in the
# next section
self._write( repr( settings ) )
# finally after all that has settled down we can do the
# headers and footers
if section.FirstHeader or section.FirstFooter :
# include the titlepg flag if the first page has a special format
self._write( r'\titlepg' )
WriteHF( section.FirstHeader, 'headerf' )
WriteHF( section.FirstFooter, 'footerf' )
WriteHF( section.Header, 'header' )
WriteHF( section.Footer, 'footer' )
# and at last the contents of the section that actually appear on the page
self._WriteElements( section )
def _WriteElements( self, elements ) :
new_line = ''
for element in elements :
self._write( new_line )
new_line = '\n'
clss = element.__class__
if clss == Paragraph :
self.WriteParagraphElement( element )
elif clss == Table :
self.WriteTableElement( element )
elif clss == StringType :
self.WriteParagraphElement( Paragraph( element ) )
elif clss in [ RawCode, Image ] :
self.WriteRawCode( element )
#elif clss == List :
# self._HandleListElement( element )
elif self.WriteCustomElement :
self.WriteCustomElement( self, element )
else :
raise Exception( "Don't know how to handle elements of type %s" % clss )
def WriteParagraphElement( self, paragraph_elem, tag_prefix='', tag_suffix=r'\par', opening='{', closing='}' ) :
# the tag_prefix and the tag_suffix take care of paragraphs in tables. A
# paragraph in a table requires and extra tag at the front (intbl) and we
# don't want the ending tag everytime. We want it for all paragraphs but
# the last.
overrides = Settings()
self._RendParagraphPropertySet( paragraph_elem.Properties, overrides )
self._RendFramePropertySet ( paragraph_elem.Frame, overrides )
self._RendShadingPropertySet ( paragraph_elem.Shading, overrides )
# when writing the RTF the style is carried from the previous paragraph to the next,
# so if the currently written paragraph has a style then make it the current one,
# otherwise leave it as it was
self._CurrentStyle = self.paragraph_style_map.get( paragraph_elem.Style, self._CurrentStyle )
self._write( r'%s\pard\plain%s %s%s ' % ( opening, tag_prefix, self._CurrentStyle, overrides ) )
for element in paragraph_elem :
if isinstance( element, StringType ) :
self._write( element )
elif isinstance( element, RawCode ) :
self._write( element.Data )
elif isinstance( element, Text ) :
self.WriteTextElement( element )
elif isinstance( element, Inline ) :
self.WriteInlineElement( element )
elif element == TAB :
self._write( r'\tab ' )
elif element == LINE :
self._write( r'\line ' )
elif self.WriteCustomElement :
self.WriteCustomElement( self, element )
else :
raise Exception( 'Don\'t know how to handle %s' % element )
self._write( tag_suffix + closing )
def WriteRawCode( self, raw_elem ) :
self._write( raw_elem.Data )
def WriteTextElement( self, text_elem ) :
overrides = Settings()
self._RendTextPropertySet ( text_elem.Properties, overrides )
self._RendShadingPropertySet( text_elem.Shading, overrides, 'ch' )
# write the wrapper and then let the custom handler have a go
if overrides : self._write( '{%s ' % repr( overrides ) )
# if the data is just a string then we can now write it
if isinstance( text_elem.Data, StringType ) :
self._write( text_elem.Data or '' )
elif text_elem.Data == TAB :
self._write( r'\tab ' )
else :
self.WriteCustomElement( self, text_elem.Data )
if overrides : self._write( '}' )
def WriteInlineElement( self, inline_elem ) :
overrides = Settings()
self._RendTextPropertySet ( inline_elem.Properties, overrides )
self._RendShadingPropertySet( inline_elem.Shading, overrides, 'ch' )
# write the wrapper and then let the custom handler have a go
if overrides : self._write( '{%s ' % repr( overrides ) )
for element in inline_elem :
# if the data is just a string then we can now write it
if isinstance( element, StringType ) :
self._write( element )
elif isinstance( element, RawCode ) :
self._write( element.Data )
elif element == TAB :
self._write( r'\tab ' )
elif element == LINE :
self._write( r'\line ' )
else :
self.WriteCustomElement( self, element )
if overrides : self._write( '}' )
def WriteText( self, text ) :
self._write( text or '' )
def WriteTableElement( self, table_elem ) :
vmerge = [ False ] * table_elem.ColumnCount
for height, cells in table_elem.Rows :
# calculate the right hand edge of the cells taking into account the spans
offset = table_elem.LeftOffset or 0
cellx = []
cell_idx = 0
for cell in cells :
cellx.append( offset + sum( table_elem.ColumnWidths[ : cell_idx + cell.Span ] ) )
cell_idx += cell.Span
self._write( r'{\trowd' )
settings = Settings()
# the spec says that this value is mandatory and I think that 108 is the default value
# so I'll take care of it here
settings.append( table_elem.GapBetweenCells or 108, 'trgaph%s' )
settings.append( TableAlignmentMap[ table_elem.Alignment ] )
settings.append( height, 'trrh%s' )
settings.append( table_elem.LeftOffset, 'trleft%s' )
width = table_elem.LeftOffset or 0
for idx, cell in enumerate( cells ) :
self._RendFramePropertySet ( cell.Frame, settings, 'cl' )
# cells don't have margins so I don't know why I was doing this
# I think it might have an affect in some versions of some WPs.
#self._RendMarginsPropertySet( cell.Margins, settings, 'cl' )
# if we are starting to merge or if this one is the first in what is
# probably a series of merges then start the vertical merging
if cell.StartVerticalMerge or (cell.VerticalMerge and not vmerge[ idx ]) :
settings.append( 'clvmgf' )
vmerge[ idx ] = True
elif cell.VerticalMerge :
#..continuing a merge
settings.append( 'clvmrg' )
else :
#..no merging going on so make sure that it is off
vmerge[ idx ] = False
# for any cell in the next row that is covered by this span we
# need to run off the vertical merging as we don't want them
# merging up into this spanned cell
for vmerge_idx in range( idx + 1, idx + cell.Span - 1 ) :
vmerge[ vmerge_idx ] = False
settings.append( CellAlignmentMap[ cell.Alignment ] )
settings.append( CellFlowMap[ cell.Flow ] )
# this terminates the definition of a cell and represents the right most edge of the cell from the left margin
settings.append( cellx[ idx ], 'cellx%s' )
self._write( repr( settings ) )
for cell in cells :
if len( cell ) :
last_idx = len( cell ) - 1
for element_idx, element in enumerate( cell ) :
# wrap plain strings in paragraph tags
if isinstance( element, StringType ) :
element = Paragraph( element )
# don't forget the prefix or else word crashes and does all sorts of strange things
if element_idx == last_idx :
self.WriteParagraphElement( element, tag_prefix=r'\intbl', tag_suffix='', opening='', closing='' )
else :
self.WriteParagraphElement( element, tag_prefix=r'\intbl', opening='', closing='' )
self._write( r'\cell' )
else :
self._write( r'\pard\intbl\cell' )
self._write( '\\row}\n' )
+94
View File
@@ -0,0 +1,94 @@
"""
A Styles is a collection of PropertySets that can be applied to a particular RTF element.
At present there are only two, Text and Paragraph but ListStyles will be added soon too.
"""
from PropertySets import *
class TextStyle :
def __init__( self, text_props, name=None, shading_props=None ) :
self.SetTextPropertySet ( text_props )
self.SetName ( name )
self.SetShadingPropertySet( shading_props )
def Copy( self ) :
return deepcopy( self )
def SetName( self, value ) :
self.Name = value
return self
def SetTextPropertySet( self, value ) :
assert isinstance( value, TextPropertySet )
self.TextPropertySet = value
return self
def SetShadingPropertySet( self, value ) :
assert value is None or isinstance( value, ShadingPropertySet )
self.ShadingPropertySet = value or ShadingPropertySet()
return self
class ParagraphStyle :
def __init__( self, name, text_style, paragraph_props=None, frame_props=None, shading_props=None ) :
# A style must have Font and a Font Size but the Text property set doesn't
# make these mandatory so that they can be used for overrides so at this point
# we need to make sure that that we have these values set
if not text_style.TextPropertySet.Font : raise Exception( 'Paragraph Styles must have a Font specified.' )
if not text_style.TextPropertySet.Size : raise Exception( 'Paragraph Styles must have a Font Size specified.' )
self.SetName ( name )
self.SetTextStyle ( text_style )
self.SetParagraphPropertySet( paragraph_props )
self.SetFramePropertySet ( frame_props )
self.SetShadingPropertySet ( shading_props )
self.SetBasedOn( None )
self.SetNext ( None )
def Copy( self ) :
return deepcopy( self )
def SetName( self, value ) :
self.Name = value
return self
def SetTextStyle( self, value ) :
assert isinstance( value, TextStyle )
self.TextStyle = value
return self
def SetParagraphPropertySet( self, value ) :
assert value is None or isinstance( value, ParagraphPropertySet )
self.ParagraphPropertySet = value or ParagraphPropertySet()
return self
def SetFramePropertySet( self, value ) :
assert value is None or isinstance( value, FramePropertySet )
self.FramePropertySet = value or FramePropertySet()
return self
def SetShadingPropertySet( self, value ) :
"""Set the background shading for the paragraph."""
assert value is None or isinstance( value, ShadingPropertySet )
self.ShadingPropertySet = value or ShadingPropertySet()
return self
def SetBasedOn( self, value ) :
"""Set the Paragraph Style that this one is based on."""
assert not value or isinstance( value, ParagraphStyle )
self.BasedOn = value
return self
def SetNext( self, value ) :
"""Set the Paragraph Style that should follow this one."""
assert not value or isinstance( value, ParagraphStyle )
self.Next = value
return self
+12
View File
@@ -0,0 +1,12 @@
from PropertySets import *
from Elements import *
from Styles import *
from Renderer import *
def dumps(doc):
import cStringIO
s=cStringIO.StringIO()
r=Renderer()
r.Write(doc,s)
return s.getvalue()
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"Contributed modules"
+689
View File
@@ -0,0 +1,689 @@
#!/usr/bin/python
# -*- coding: latin-1 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Pythonic simple SOAP Client implementation"
__author__ = "Mariano Reingart (reingart@gmail.com)"
__copyright__ = "Copyright (C) 2008 Mariano Reingart"
__license__ = "LGPL 3.0"
__version__ = "1.02c"
import urllib
try:
import httplib2
Http = httplib2.Http
except ImportError:
import urllib2
class Http(): # wrapper to use when httplib2 not available
def request(self, url, method, body, headers):
f = urllib2.urlopen(urllib2.Request(url, body, headers))
return f.info(), f.read()
from simplexml import SimpleXMLElement, TYPE_MAP, OrderedDict
class SoapFault(RuntimeError):
def __init__(self,faultcode,faultstring):
self.faultcode = faultcode
self.faultstring = faultstring
# soap protocol specification & namespace
soap_namespaces = dict(
soap11="http://schemas.xmlsoap.org/soap/envelope/",
soap="http://schemas.xmlsoap.org/soap/envelope/",
soapenv="http://schemas.xmlsoap.org/soap/envelope/",
soap12="http://www.w3.org/2003/05/soap-env",
)
class SoapClient(object):
"Simple SOAP Client (s�mil PHP)"
def __init__(self, location = None, action = None, namespace = None,
cert = None, trace = False, exceptions = True, proxy = None, ns=False,
soap_ns=None, wsdl = None, cache = False):
self.certssl = cert
self.keyssl = None
self.location = location # server location (url)
self.action = action # SOAP base action
self.namespace = namespace # message
self.trace = trace # show debug messages
self.exceptions = exceptions # lanzar execpiones? (Soap Faults)
self.xml_request = self.xml_response = ''
if not soap_ns and not ns:
self.__soap_ns = 'soap' # 1.1
elif not soap_ns and ns:
self.__soap_ns = 'soapenv' # 1.2
else:
self.__soap_ns = soap_ns
# parse wsdl url
self.services = wsdl and self.wsdl(wsdl, debug=trace, cache=cache)
self.service_port = None # service port for late binding
if not proxy:
self.http = Http()
else:
import socks
##httplib2.debuglevel=4
self.http = httplib2.Http(proxy_info = httplib2.ProxyInfo(
proxy_type=socks.PROXY_TYPE_HTTP, **proxy))
#if self.certssl: # esto funciona para validar al server?
# self.http.add_certificate(self.keyssl, self.keyssl, self.certssl)
self.__ns = ns # namespace prefix or False to not use it
if not ns:
self.__xml = """<?xml version="1.0" encoding="UTF-8"?>
<%(soap_ns)s:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:%(soap_ns)s="%(soap_uri)s">
<%(soap_ns)s:Body>
<%(method)s xmlns="%(namespace)s">
</%(method)s>
</%(soap_ns)s:Body>
</%(soap_ns)s:Envelope>"""
else:
self.__xml = """<?xml version="1.0" encoding="UTF-8"?>
<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" xmlns:%(ns)s="%(namespace)s">
<%(soap_ns)s:Header/>
<%(soap_ns)s:Body>
<%(ns)s:%(method)s>
</%(ns)s:%(method)s>
</%(soap_ns)s:Body>
</%(soap_ns)s:Envelope>"""
def __getattr__(self, attr):
"Return a pseudo-method that can be called"
if not self.services: # not using WSDL?
return lambda self=self, *args, **kwargs: self.call(attr,*args,**kwargs)
else: # using WSDL:
return lambda self=self, *args, **kwargs: self.wsdl_call(attr,*args,**kwargs)
def call(self, method, *args, **kwargs):
"Prepare xml request and make SOAP call, returning a SimpleXMLElement"
#TODO: method != input_message
# Basic SOAP request:
xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns,
soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns])
request = SimpleXMLElement(xml,namespace=self.__ns and self.namespace, prefix=self.__ns)
# serialize parameters
if kwargs:
parameters = kwargs.items()
else:
parameters = args
if parameters and isinstance(parameters[0], SimpleXMLElement):
# merge xmlelement parameter ("raw" - already marshalled)
for param in parameters[0].children():
getattr(request,method).import_node(param)
else:
# marshall parameters:
for k,v in parameters: # dict: tag=valor
getattr(request,method).marshall(k,v)
self.xml_request = request.as_xml()
self.xml_response = self.send(method, self.xml_request)
response = SimpleXMLElement(self.xml_response, namespace=self.namespace)
if self.exceptions and response("Fault", ns=soap_namespaces.values(), error=False):
raise SoapFault(unicode(response.faultcode), unicode(response.faultstring))
return response
def send(self, method, xml):
"Send SOAP request using HTTP"
if self.location == 'test': return
location = "%s" % self.location #?op=%s" % (self.location, method)
if self.services:
soap_action = self.action
else:
soap_action = self.action+method
headers={
'Content-type': 'text/xml; charset="UTF-8"',
'Content-length': str(len(xml)),
"SOAPAction": "\"%s\"" % (soap_action)
}
if self.trace:
print "-"*80
print "POST %s" % location
print '\n'.join(["%s: %s" % (k,v) for k,v in headers.items()])
print u"\n%s" % xml.decode("utf8","ignore")
response, content = self.http.request(
location,"POST", body=xml, headers=headers )
self.response = response
self.content = content
if self.trace:
print
print '\n'.join(["%s: %s" % (k,v) for k,v in response.items()])
print content#.decode("utf8","ignore")
print "="*80
return content
def get_operation(self, method):
# try to find operation in wsdl file
soap_ver = self.__soap_ns == 'soap12' and 'soap12' or 'soap11'
if not self.service_port:
for service_name, service in self.services.items():
for port_name, port in [port for port in service['ports'].items()]:
if port['soap_ver'] == soap_ver:
self.service_port = service_name, port_name
break
else:
raise RuntimeError("Cannot determine service in WSDL: "
"SOAP version: %s" % soap_ver)
else:
port = self.services[self.service_port[0]]['ports'][self.service_port[1]]
self.location = port['location']
operation = port['operations'].get(unicode(method))
if not operation:
raise RuntimeError("Operation %s not found in WSDL: "
"Service/Port Type: %s" %
(method, self.service_port))
return operation
def wsdl_call(self, method, *args, **kwargs):
"Pre and post process SOAP call, input and output parameters using WSDL"
soap_uri = soap_namespaces[self.__soap_ns]
operation = self.get_operation(method)
# get i/o type declarations:
input = operation['input']
output = operation['output']
if 'action' in operation:
self.action = operation['action']
# sort parameters (same order as xsd:sequence)
def sort_dict(od, d):
if isinstance(od, dict):
ret = OrderedDict()
for k in od.keys():
v = d.get(k)
if v:
if isinstance(v, dict):
v = sort_dict(od[k], v)
elif isinstance(v, list):
v = [sort_dict(od[k][0], v1)
for v1 in v]
ret[str(k)] = v
return ret
else:
return d
if input and kwargs:
params = sort_dict(input.values()[0], kwargs).items()
method = input.keys()[0]
#elif not input:
#TODO: no message! (see wsmtxca.dummy)
else:
params = kwargs and kwargs.items()
# call remote procedure
response = self.call(method, *params)
# parse results:
resp = response('Body',ns=soap_uri).children().unmarshall(output)
return resp and resp.values()[0] # pass Response tag children
def help(self, method):
"Return operation documentation and invocation/returned value example"
operation = self.get_operation(method)
input = operation['input'].values()
input = input and input[0]
output = operation['output'].values()[0]
return u"%s(%s)\n -> %s:\n\n%s" % (
method,
input and ", ".join("%s=%s" % (k,repr(v)) for k,v
in input.items()) or "",
output and output or "",
operation.get("documentation",""),
)
def wsdl(self, url, debug=False, cache=False):
"Parse Web Service Description v1.1"
soap_ns = {
"http://schemas.xmlsoap.org/wsdl/soap/": 'soap11',
"http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12',
}
wsdl_uri="http://schemas.xmlsoap.org/wsdl/"
xsd_uri="http://www.w3.org/2001/XMLSchema"
xsi_uri="http://www.w3.org/2001/XMLSchema-instance"
get_local_name = lambda s: str((':' in s) and s.split(':')[1] or s)
REVERSE_TYPE_MAP = dict([(v,k) for k,v in TYPE_MAP.items()])
def fetch(url):
"Fetch a document from a URL, save it locally if cache enabled"
import os, hashlib
# make md5 hash of the url for caching...
filename = "%s.xml" % hashlib.md5(url).hexdigest()
if isinstance(cache, basestring):
filename = os.path.join(cache, filename)
if cache and os.path.exists(filename):
if debug: print "Reading file %s" % (filename, )
f = open(filename, "r")
xml = f.read()
f.close()
else:
if debug: print "Fetching url %s" % (url, )
f = urllib.urlopen(url)
xml = f.read()
if cache:
if debug: print "Writing file %s" % (filename, )
f = open(filename, "w")
f.write(xml)
f.close()
return xml
# Open uri and read xml:
xml = fetch(url)
# Parse WSDL XML:
wsdl = SimpleXMLElement(xml, namespace=wsdl_uri)
# detect soap prefix and uri (xmlns attributes of <definitions>)
xsd_ns = None
soap_uris = {}
for k, v in wsdl[:]:
if v in soap_ns and k.startswith("xmlns:"):
soap_uris[get_local_name(k)] = v
if v== xsd_uri and k.startswith("xmlns:"):
xsd_ns = get_local_name(k)
# Extract useful data:
self.namespace = wsdl['targetNamespace']
self.documentation = unicode(wsdl('documentation', error=False) or '')
services = {}
bindings = {} # binding_name: binding
operations = {} # operation_name: operation
port_type_bindings = {} # port_type_name: binding
messages = {} # message: element
elements = {} # element: type def
for service in wsdl.service:
service_name=service['name']
if not service_name:
continue # empty service?
if debug: print "Processing service", service_name
serv = services.setdefault(service_name, {'ports': {}})
serv['documentation']=service['documentation'] or ''
for port in service.port:
binding_name = get_local_name(port['binding'])
address = port('address', ns=soap_uris.values(), error=False)
location = address and address['location'] or None
soap_uri = address and soap_uris.get(address.get_prefix())
soap_ver = soap_uri and soap_ns.get(soap_uri)
bindings[binding_name] = {'service_name': service_name,
'location': location,
'soap_uri': soap_uri, 'soap_ver': soap_ver,
}
serv['ports'][port['name']] = bindings[binding_name]
for binding in wsdl.binding:
binding_name = binding['name']
if debug: print "Processing binding", service_name
soap_binding = binding('binding', ns=soap_uris.values(), error=False)
transport = soap_binding and soap_binding['transport'] or None
port_type_name = get_local_name(binding['type'])
bindings[binding_name].update({
'port_type_name': port_type_name,
'transport': transport, 'operations': {},
})
port_type_bindings[port_type_name] = bindings[binding_name]
for operation in binding.operation:
op_name = operation['name']
op = operation('operation',ns=soap_uris.values(), error=False)
action = op and op['soapAction']
d = operations.setdefault(op_name, {})
bindings[binding_name]['operations'][op_name] = d
d.update({'name': op_name})
#if action: #TODO: separe operation_binding from operation
if action:
d["action"] = action
#TODO: cleanup element/schema/types parsing:
def process_element(element_name, node):
"Parse and define simple element types"
if debug: print "Processing element", element_name
for tag in node:
if tag.get_local_name() in ("annotation", "documentation"):
continue
elif tag.get_local_name() in ('element', 'restriction'):
if debug: print element_name,"has not children!",tag
children = tag # element "alias"?
alias = True
elif tag.children():
children = tag.children()
alias = False
else:
if debug: print element_name,"has not children!",tag
continue #TODO: abstract?
d = OrderedDict()
for e in children:
t = e['type']
if not t:
t = e['base'] # complexContent (extension)!
if not t:
t = 'anyType' # no type given!
t = t.split(":")
if len(t)>1:
ns, type_name = t
else:
ns, type_name = None, t[0]
if element_name == type_name:
continue # prevent infinite recursion
uri = ns and e.get_namespace_uri(ns) or xsd_uri
if uri==xsd_uri:
# look for the type, None == any
fn = REVERSE_TYPE_MAP.get(unicode(type_name), None)
else:
# complex type, postprocess later
fn = elements.setdefault(unicode(type_name), OrderedDict())
if e['name'] is not None and not alias:
e_name = unicode(e['name'])
d[e_name] = fn
else:
if debug: print "complexConent/simpleType/element", element_name, "=", type_name
d[None] = fn
if e['maxOccurs']=="unbounded":
# it's an array... TODO: compound arrays?
d.array = True
if e is not None and e.get_local_name() == 'extension' and e.children():
# extend base element:
process_element(element_name, e.children())
elements.setdefault(element_name, OrderedDict()).update(d)
# check axis2 namespace at schema types attributes
self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get('targetNamespace', self.namespace)
imported_schemas = {}
def preprocess_schema(schema):
"Find schema elements and complex types"
for element in schema.children():
if element.get_local_name() in ('import', ):
schema_namespace = element['namespace']
schema_location = element['schemaLocation']
if schema_location is None:
if debug: print "Schema location not provided for %s!" % (schema_namespace, )
continue
if schema_location in imported_schemas:
if debug: print "Schema %s already imported!" % (schema_location, )
continue
imported_schemas[schema_location] = schema_namespace
if debug: print "Importing schema %s from %s" % (schema_namespace, schema_location)
# Open uri and read xml:
xml = fetch(schema_location)
# Parse imported XML schema (recursively):
imported_schema = SimpleXMLElement(xml, namespace=xsd_uri)
preprocess_schema(imported_schema)
if element.get_local_name() in ('element', 'complexType', "simpleType"):
element_name = unicode(element['name'])
if debug: print "Parsing Element %s: %s" % (element.get_local_name(),element_name)
if element.get_local_name() == 'complexType':
children = element.children()
elif element.get_local_name() == 'simpleType':
children = element("restriction", ns=xsd_uri)
elif element.get_local_name() == 'element' and element['type']:
children = element
else:
children = element.children()
if children:
children = children.children()
elif element.get_local_name() == 'element':
children = element
if children:
process_element(element_name, children)
def postprocess_element(elements):
"Fix unresolved references (elements referenced before its definition, thanks .net)"
for k,v in elements.items():
if isinstance(v, OrderedDict):
if v.array:
elements[k] = [v] # convert arrays to python lists
if v!=elements: #TODO: fix recursive elements
postprocess_element(v)
if None in v and v[None]: # extension base?
if isinstance(v[None], dict):
for i, kk in enumerate(v[None]):
# extend base -keep orginal order-
elements[k].insert(kk, v[None][kk], i)
del v[None]
else: # "alias", just replace
if debug: print "Replacing ", k , " = ", v[None]
elements[k] = v[None]
#break
if isinstance(v, list):
for n in v: # recurse list
postprocess_element(n)
# process current wsdl schema:
for schema in wsdl.types("schema", ns=xsd_uri):
preprocess_schema(schema)
postprocess_element(elements)
for message in wsdl.message:
if debug: print "Processing message", message['name']
part = message('part', error=False)
element = {}
if part:
element_name = part['element']
if not element_name:
element_name = part['type'] # some uses type instead
element_name = get_local_name(element_name)
element = {element_name: elements.get(element_name)}
messages[message['name']] = element
for port_type in wsdl.portType:
port_type_name = port_type['name']
if debug: print "Processing port type", port_type_name
binding = port_type_bindings[port_type_name]
for operation in port_type.operation:
op_name = operation['name']
op = operations[op_name]
op['documentation'] = unicode(operation('documentation', error=False) or '')
if binding['soap_ver']:
#TODO: separe operation_binding from operation (non SOAP?)
input = get_local_name(operation.input['message'])
output = get_local_name(operation.output['message'])
op['input'] = messages[input]
op['output'] = messages[output]
if debug:
import pprint
pprint.pprint(services)
return services
def parse_proxy(proxy_str):
"Parses proxy address user:pass@host:port into a dict suitable for httplib2"
proxy_dict = {}
if proxy_str is None:
return
if "@" in proxy_str:
user_pass, host_port = proxy_str.split("@")
else:
user_pass, host_port = "", proxy_str
if ":" in host_port:
host, port = host_port.split(":")
proxy_dict['proxy_host'], proxy_dict['proxy_port'] = host, int(port)
if ":" in user_pass:
proxy_dict['proxy_user'], proxy_dict['proxy_pass'] = user_pass.split(":")
return proxy_dict
if __name__=="__main__":
import sys
if '--web2py' in sys.argv:
# test local sample webservice exposed by web2py
from client import SoapClient
if not '--wsdl' in sys.argv:
client = SoapClient(
location = "http://127.0.0.1:8000/webservices/sample/call/soap",
action = 'http://127.0.0.1:8000/webservices/sample/call/soap', # SOAPAction
namespace = "http://127.0.0.1:8000/webservices/sample/call/soap",
soap_ns='soap', trace = True, ns = False, exceptions=True)
else:
client = SoapClient(wsdl="http://127.0.0.1:8000/webservices/sample/call/soap?WSDL",trace=True)
response = client.Dummy()
print 'dummy', response
response = client.Echo(value='hola')
print 'echo', repr(response)
response = client.AddIntegers(a=1,b=2)
if not '--wsdl' in sys.argv:
result = response.AddResult # manully convert returned type
print int(result)
else:
result = response['AddResult']
print result, type(result), "auto-unmarshalled"
if '--raw' in sys.argv:
# raw (unmarshalled parameter) local sample webservice exposed by web2py
from client import SoapClient
client = SoapClient(
location = "http://127.0.0.1:8000/webservices/sample/call/soap",
action = 'http://127.0.0.1:8000/webservices/sample/call/soap', # SOAPAction
namespace = "http://127.0.0.1:8000/webservices/sample/call/soap",
soap_ns='soap', trace = True, ns = False)
params = SimpleXMLElement("""<?xml version="1.0" encoding="UTF-8"?><AddIntegers><a>3</a><b>2</b></AddIntegers>""") # manully convert returned type
response = client.call('AddIntegers',params)
result = response.AddResult
print int(result) # manully convert returned type
if '--ctg' in sys.argv:
# test AFIP Agriculture webservice
client = SoapClient(
location = "https://fwshomo.afip.gov.ar/wsctg/services/CTGService",
action = 'http://impl.service.wsctg.afip.gov.ar/CTGService/', # SOAPAction
namespace = "http://impl.service.wsctg.afip.gov.ar/CTGService/",
trace = True,
ns = True)
response = client.dummy()
result = response.dummyResponse
print str(result.appserver)
print str(result.dbserver)
print str(result.authserver)
if '--wsfe' in sys.argv:
# Demo & Test (AFIP Electronic Invoice):
ta_file = open("TA.xml")
try:
ta_string = ta_file.read() # read access ticket (wsaa.py)
finally:
ta_file.close()
ta = SimpleXMLElement(ta_string)
token = str(ta.credentials.token)
sign = str(ta.credentials.sign)
cuit = long(20267565393)
id = 1234
cbte =199
client = SoapClient(
location = "https://wswhomo.afip.gov.ar/wsfe/service.asmx",
action = 'http://ar.gov.afip.dif.facturaelectronica/', # SOAPAction
namespace = "http://ar.gov.afip.dif.facturaelectronica/",
trace = True)
results = client.FERecuperaQTYRequest(
argAuth= {"Token": token, "Sign": sign, "cuit":long(cuit)}
)
if int(results.FERecuperaQTYRequestResult.RError.percode) != 0:
print "Percode: %s" % results.FERecuperaQTYRequestResult.RError.percode
print "MSGerror: %s" % results.FERecuperaQTYRequestResult.RError.perrmsg
else:
print int(results.FERecuperaQTYRequestResult.qty.value)
if '--feriados' in sys.argv:
# Demo & Test: Argentina Holidays (Ministerio del Interior):
# this webservice seems disabled
from datetime import datetime, timedelta
client = SoapClient(
location = "http://webservices.mininterior.gov.ar/Feriados/Service.svc",
action = 'http://tempuri.org/IMyService/', # SOAPAction
namespace = "http://tempuri.org/FeriadoDS.xsd",
trace = True)
dt1 = datetime.today() - timedelta(days=60)
dt2 = datetime.today() + timedelta(days=60)
feriadosXML = client.FeriadosEntreFechasas_xml(dt1=dt1.isoformat(), dt2=dt2.isoformat());
print feriadosXML
if '--wsdl-parse' in sys.argv:
client = SoapClient()
# Test PySimpleSOAP WSDL
client.wsdl("file:C:/test.wsdl", debug=True)
# Test Java Axis WSDL:
client.wsdl('https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl',debug=True)
# Test .NET 2.0 WSDL:
client.wsdl('https://wswhomo.afip.gov.ar/wsfe/service.asmx?WSDL',debug=True)
client.wsdl('https://wswhomo.afip.gov.ar/wsfex/service.asmx?WSDL',debug=True)
client.wsdl('https://testdia.afip.gov.ar/Dia/Ws/wDigDepFiel/wDigDepFiel.asmx?WSDL',debug=True)
# Test JBoss WSDL:
client.wsdl('https://fwshomo.afip.gov.ar/wsctg/services/CTGService?wsdl',debug=True)
client.wsdl('https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl',debug=True)
if '--wsdl-client' in sys.argv:
client = SoapClient(wsdl='https://wswhomo.afip.gov.ar/wsfex/service.asmx?WSDL',trace=True)
results = client.FEXDummy()
print results['FEXDummyResult']['AppServer']
print results['FEXDummyResult']['DbServer']
print results['FEXDummyResult']['AuthServer']
ta_file = open("TA.xml")
try:
ta_string = ta_file.read() # read access ticket (wsaa.py)
finally:
ta_file.close()
ta = SimpleXMLElement(ta_string)
token = str(ta.credentials.token)
sign = str(ta.credentials.sign)
response = client.FEXGetCMP(
Auth={"Token": token, "Sign": sign, "Cuit": 20267565393},
Cmp={"Tipo_cbte": 19, "Punto_vta": 1, "Cbte_nro": 1})
result = response['FEXGetCMPResult']
if False: print result
if 'FEXErr' in result:
print "FEXError:", result['FEXErr']['ErrCode'], result['FEXErr']['ErrCode']
cbt = result['FEXResultGet']
print cbt['Cae']
FEX_event = result['FEXEvents']
print FEX_event['EventCode'], FEX_event['EventMsg']
if '--wsdl-ctg' in sys.argv:
client = SoapClient(wsdl='https://fwshomo.afip.gov.ar/wsctg/services/CTGService?wsdl',
trace=True, ns = "ctg")
results = client.dummy()
print results
print results['DummyResponse']['appserver']
print results['DummyResponse']['dbserver']
print results['DummyResponse']['authserver']
ta_file = open("TA.xml")
try:
ta_string = ta_file.read() # read access ticket (wsaa.py)
finally:
ta_file.close()
ta = SimpleXMLElement(ta_string)
token = str(ta.credentials.token)
sign = str(ta.credentials.sign)
print client.help("obtenerProvincias")
response = client.obtenerProvincias(auth={"token":token, "sign":sign, "cuitRepresentado":20267565393})
print "response=",response
for ret in response:
print ret['return']['codigoProvincia'], ret['return']['descripcionProvincia'].encode("latin1")
prueba = dict(numeroCartaDePorte=512345678, codigoEspecie=23,
cuitRemitenteComercial=20267565393, cuitDestino=20267565393, cuitDestinatario=20267565393,
codigoLocalidadOrigen=3058, codigoLocalidadDestino=3059,
codigoCosecha='0910', pesoNetoCarga=1000, cantHoras=1,
patenteVehiculo='CZO985', cuitTransportista=20267565393,
numeroCTG="43816783", transaccion='10000001681', observaciones='',
)
response = client.solicitarCTG(
auth={"token": token, "sign": sign, "cuitRepresentado": 20267565393},
solicitarCTGRequest= prueba)
print response['return']['numeroCTG']
##print parse_proxy(None)
##print parse_proxy("host:1234")
##print parse_proxy("user:pass@host:1234")
##sys.exit(0)
+455
View File
@@ -0,0 +1,455 @@
#!/usr/bin/python
# -*- coding: latin-1 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Simple SOAP Server implementation"
__author__ = "Mariano Reingart (reingart@gmail.com)"
__copyright__ = "Copyright (C) 2010 Mariano Reingart"
__license__ = "LGPL 3.0"
__version__ = "1.02c"
from simplexml import SimpleXMLElement, TYPE_MAP, DateTime, Date, Decimal
DEBUG = False
class SoapDispatcher(object):
"Simple Dispatcher for SOAP Server"
def __init__(self, name, documentation='', action='', location='',
namespace=None, prefix=False,
soap_uri="http://schemas.xmlsoap.org/soap/envelope/",
soap_ns='soap',
**kwargs):
self.methods = {}
self.name = name
self.documentation = documentation
self.action = action # base SoapAction
self.location = location
self.namespace = namespace # targetNamespace
self.prefix = prefix
self.soap_ns = soap_ns
self.soap_uri = soap_uri
def register_function(self, name, fn, returns=None, args=None, doc=None):
self.methods[name] = fn, returns, args, doc or getattr(fn,"__doc__","")
def dispatch(self, xml, action=None):
"Receive and proccess SOAP call"
# default values:
prefix = self.prefix
ret = fault = None
soap_ns, soap_uri = self.soap_ns, self.soap_uri
soap_fault_code = 'VersionMismatch'
try:
request = SimpleXMLElement(xml, namespace=self.namespace)
# detect soap prefix and uri (xmlns attributes of Envelope)
for k, v in request[:]:
if v in ("http://schemas.xmlsoap.org/soap/envelope/",
"http://www.w3.org/2003/05/soap-env",):
soap_ns = request.attributes()[k].localName
soap_uri = request.attributes()[k].value
soap_fault_code = 'Client'
# parse request message and get local method
method = request('Body', ns=soap_uri).children()(0)
if action:
# method name = action
name = action[len(self.action)+1:-1]
prefix = self.prefix
if not action or not name:
# method name = input message name
name = method.get_local_name()
prefix = method.get_prefix()
if DEBUG: print "dispatch method", name
function, returns_types, args_types, doc = self.methods[name]
# de-serialize parameters (if type definitions given)
if args_types:
args = method.children().unmarshall(args_types)
elif args_types is None:
args = {'request':method} # send raw request
else:
args = {} # no parameters
soap_fault_code = 'Server'
# execute function
ret = function(**args)
if DEBUG: print ret
except Exception, e:
import sys
etype, evalue, etb = sys.exc_info()
if DEBUG:
import traceback
detail = ''.join(traceback.format_exception(etype, evalue, etb))
detail += '\n\nXML REQUEST\n\n' + xml
else:
detail = None
fault = {'faultcode': "%s.%s" % (soap_fault_code, etype.__name__),
'faultstring': unicode(evalue),
'detail': detail}
# build response message
if not prefix:
xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>"""
else:
xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"
xmlns:%(prefix)s="%(namespace)s"/>"""
xml = xml % {'namespace': self.namespace, 'prefix': prefix,
'soap_ns': soap_ns, 'soap_uri': soap_uri}
response = SimpleXMLElement(xml, namespace=self.namespace,
prefix=prefix)
response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema"
body = response.add_child("%s:Body" % soap_ns, ns=False)
if fault:
# generate a Soap Fault (with the python exception)
body.marshall("%s:Fault" % soap_ns, fault, ns=False)
else:
# return normal value
res = body.add_child("%sResponse" % name, ns=prefix)
if not prefix:
res['xmlns'] = self.namespace # add target namespace
# serialize returned values (response) if type definition available
if returns_types:
if not isinstance(ret, dict):
res.marshall(returns_types.keys()[0], ret, )
else:
for k,v in ret.items():
res.marshall(k, v)
elif returns_types is None:
# merge xmlelement returned
res.import_node(ret)
return response.as_xml()
# Introspection functions:
def list_methods(self):
"Return a list of aregistered operations"
return [(method, doc) for method, (function, returns, args, doc) in self.methods.items()]
def help(self, method=None):
"Generate sample request and response messages"
(function, returns, args, doc) = self.methods[method]
xml = """
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><%(method)s xmlns="%(namespace)s"/></soap:Body>
</soap:Envelope>""" % {'method':method, 'namespace':self.namespace}
request = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix)
if args:
items = args.items()
elif args is None:
items = [('value', None)]
else:
items = []
for k,v in items:
request(method).marshall(k, v, add_comments=True, ns=False)
xml = """
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><%(method)sResponse xmlns="%(namespace)s"/></soap:Body>
</soap:Envelope>""" % {'method':method, 'namespace':self.namespace}
response = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix)
if returns:
items = returns.items()
elif args is None:
items = [('value', None)]
else:
items = []
for k,v in items:
response('%sResponse'%method).marshall(k, v, add_comments=True, ns=False)
return request.as_xml(pretty=True), response.as_xml(pretty=True), doc
def wsdl(self):
"Generate Web Service Description v1.1"
xml = """<?xml version="1.0"?>
<wsdl:definitions name="%(name)s"
targetNamespace="%(namespace)s"
xmlns:tns="%(namespace)s"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">%(documentation)s</wsdl:documentation>
<wsdl:types>
<xsd:schema targetNamespace="%(namespace)s"
elementFormDefault="qualified"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
</xsd:schema>
</wsdl:types>
</wsdl:definitions>
""" % {'namespace': self.namespace, 'name': self.name, 'documentation': self.documentation}
wsdl = SimpleXMLElement(xml)
for method, (function, returns, args, doc) in self.methods.items():
# create elements:
def parse_element(name, values, array=False, complex=False):
if not complex:
element = wsdl('wsdl:types')('xsd:schema').add_child('xsd:element')
complex = element.add_child("xsd:complexType")
else:
complex = wsdl('wsdl:types')('xsd:schema').add_child('xsd:complexType')
element = complex
element['name'] = name
if values:
items = values
elif values is None:
items = [('value', None)]
else:
items = []
if not array and items:
all = complex.add_child("xsd:all")
elif items:
all = complex.add_child("xsd:sequence")
for k,v in items:
e = all.add_child("xsd:element")
e['name'] = k
if array:
e[:]={'minOccurs': "0", 'maxOccurs': "unbounded"}
if v in TYPE_MAP.keys():
t='xsd:%s' % TYPE_MAP[v]
elif v is None:
t='xsd:anyType'
elif isinstance(v, list):
n="ArrayOf%s%s" % (name, k)
l = []
for d in v:
l.extend(d.items())
parse_element(n, l, array=True, complex=True)
t = "tns:%s" % n
elif isinstance(v, dict):
n="%s%s" % (name, k)
parse_element(n, v.items(), complex=True)
t = "tns:%s" % n
e.add_attribute('type', t)
parse_element("%s" % method, args and args.items())
parse_element("%sResponse" % method, returns and returns.items())
# create messages:
for m,e in ('Input',''), ('Output','Response'):
message = wsdl.add_child('wsdl:message')
message['name'] = "%s%s" % (method, m)
part = message.add_child("wsdl:part")
part[:] = {'name': 'parameters',
'element': 'tns:%s%s' % (method,e)}
# create ports
portType = wsdl.add_child('wsdl:portType')
portType['name'] = "%sPortType" % self.name
for method, (function, returns, args, doc) in self.methods.items():
op = portType.add_child('wsdl:operation')
op['name'] = method
if doc:
op.add_child("wsdl:documentation", doc)
input = op.add_child("wsdl:input")
input['message'] = "tns:%sInput" % method
output = op.add_child("wsdl:output")
output['message'] = "tns:%sOutput" % method
# create bindings
binding = wsdl.add_child('wsdl:binding')
binding['name'] = "%sBinding" % self.name
binding['type'] = "tns:%sPortType" % self.name
soapbinding = binding.add_child('soap:binding')
soapbinding['style'] = "document"
soapbinding['transport'] = "http://schemas.xmlsoap.org/soap/http"
for method in self.methods.keys():
op = binding.add_child('wsdl:operation')
op['name'] = method
soapop = op.add_child('soap:operation')
soapop['soapAction'] = self.action + method
soapop['style'] = 'document'
input = op.add_child("wsdl:input")
##input.add_attribute('name', "%sInput" % method)
soapbody = input.add_child("soap:body")
soapbody["use"] = "literal"
output = op.add_child("wsdl:output")
##output.add_attribute('name', "%sOutput" % method)
soapbody = output.add_child("soap:body")
soapbody["use"] = "literal"
service = wsdl.add_child('wsdl:service')
service["name"] = "%sService" % self.name
service.add_child('wsdl:documentation', text=self.documentation)
port=service.add_child('wsdl:port')
port["name"] = "%s" % self.name
port["binding"] = "tns:%sBinding" % self.name
soapaddress = port.add_child('soap:address')
soapaddress["location"] = self.location
return wsdl.as_xml(pretty=True)
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
class SOAPHandler(BaseHTTPRequestHandler):
def do_GET(self):
"User viewable help information and wsdl"
args = self.path[1:].split("?")
print "serving", args
if self.path != "/" and args[0] not in self.server.dispatcher.methods.keys():
self.send_error(404, "Method not found: %s" % args[0])
else:
if self.path == "/":
# return wsdl if no method supplied
response = self.server.dispatcher.wsdl()
else:
# return supplied method help (?request or ?response messages)
req, res, doc = self.server.dispatcher.help(args[0])
if len(args)==1 or args[1]=="request":
response = req
else:
response = res
self.send_response(200)
self.send_header("Content-type", "text/xml")
self.end_headers()
self.wfile.write(response)
def do_POST(self):
"SOAP POST gateway"
self.send_response(200)
self.send_header("Content-type", "text/xml")
self.end_headers()
request = self.rfile.read(int(self.headers.getheader('content-length')))
response = self.server.dispatcher.dispatch(request)
self.wfile.write(response)
if __name__=="__main__":
import sys
dispatcher = SoapDispatcher(
name = "PySimpleSoapSample",
location = "http://localhost:8008/",
action = 'http://localhost:8008/', # SOAPAction
namespace = "http://example.com/pysimplesoapsamle/", prefix="ns0",
documentation = 'Example soap service using PySimpleSoap',
trace = True,
ns = True)
def adder(p,c, dt=None):
"Add several values"
print c[0]['d'],c[1]['d'],
import datetime
dt = dt + datetime.timedelta(365)
return {'ab': p['a']+p['b'], 'dd': c[0]['d']+c[1]['d'], 'dt': dt}
def dummy(in0):
"Just return input"
return in0
def echo(request):
"Copy request->response (generic, any type)"
return request.value
dispatcher.register_function('Adder', adder,
returns={'AddResult': {'ab': int, 'dd': str } },
args={'p': {'a': int,'b': int}, 'dt': Date, 'c': [{'d': Decimal}]})
dispatcher.register_function('Dummy', dummy,
returns={'out0': str},
args={'in0': str})
dispatcher.register_function('Echo', echo)
if '--local' in sys.argv:
wsdl=dispatcher.wsdl()
print wsdl
testfile = open("C:/test.wsdl","w")
try:
testfile.write(wsdl)
finally:
testfile.close()
# dummy local test (clasic soap dialect)
xml = """<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Adder xmlns="http://example.com/sample.wsdl">
<p><a>1</a><b>2</b></p><c><d>5000000.1</d><d>.2</d></c><dt>20100724</dt>
</Adder>
</soap:Body>
</soap:Envelope>"""
print dispatcher.dispatch(xml)
# dummy local test (modern soap dialect, SoapUI)
xml = """
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pys="http://example.com/pysimplesoapsamle/">
<soapenv:Header/>
<soapenv:Body>
<pys:Adder>
<pys:p><pys:a>9</pys:a><pys:b>3</pys:b></pys:p>
<pys:dt>19690720<!--1969-07-20T21:28:00--></pys:dt>
<pys:c><pys:d>10.001</pys:d><pys:d>5.02</pys:d></pys:c>
</pys:Adder>
</soapenv:Body>
</soapenv:Envelope>
"""
print dispatcher.dispatch(xml)
# echo local test (generic soap service)
xml = """<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<Echo xmlns="http://example.com/sample.wsdl">
<value xsi:type="xsd:string">Hello world</value>
</Echo>
</soap:Body>
</soap:Envelope>"""
print dispatcher.dispatch(xml)
for method, doc in dispatcher.list_methods():
request, response, doc = dispatcher.help(method)
##print request
##print response
if '--serve' in sys.argv:
print "Starting server..."
httpd = HTTPServer(("", 8008), SOAPHandler)
httpd.dispatcher = dispatcher
httpd.serve_forever()
if '--consume' in sys.argv:
from client import SoapClient
client = SoapClient(
location = "http://localhost:8008/",
action = 'http://localhost:8008/', # SOAPAction
namespace = "http://example.com/sample.wsdl",
soap_ns='soap',
trace = True,
ns = False)
response = client.Adder(p={'a':1,'b':2},dt='20100724',c=[{'d':'1.20'},{'d':'2.01'}])
result = response.AddResult
print int(result.ab)
print str(result.dd)
+416
View File
@@ -0,0 +1,416 @@
#!/usr/bin/python
# -*- coding: latin-1 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Simple XML manipulation"
__author__ = "Mariano Reingart (reingart@gmail.com)"
__copyright__ = "Copyright (C) 2008/009 Mariano Reingart"
__license__ = "LGPL 3.0"
__version__ = "1.02c"
import xml.dom.minidom
from decimal import Decimal
import datetime
import time
DEBUG = False
# Functions to serialize/unserialize special immutable types:
datetime_u = lambda s: datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%S")
datetime_m = lambda dt: dt.isoformat('T')
date_u = lambda s: datetime.datetime.strptime(s[0:10], "%Y-%m-%d").date()
date_m = lambda d: d.strftime("%Y-%m-%d")
time_u = lambda s: datetime.datetime.strptime(s, "%H:%M:%S").time()
time_m = lambda d: d.strftime("%H%M%S")
bool_u = lambda s: {'0':False, 'false': False, '1': True, 'true': True}[s]
# aliases:
class Alias():
def __init__(self, py_type, xml_type):
self.py_type, self.xml_type = py_type, xml_type
def __call__(self, value):
return self.py_type(value)
def __repr__(self):
return "<alias '%s' for '%s'>" % (self.xml_type, self.py_type)
byte = Alias(str,'byte')
short = Alias(int,'short')
double = Alias(float,'double')
integer = Alias(long,'integer')
DateTime = datetime.datetime
Date = datetime.date
Time = datetime.time
# Define convertion function (python type): xml schema type
TYPE_MAP = {str:'string',unicode:'string',
bool:'boolean', short:'short', byte:'byte',
int:'int', long:'long', integer:'integer',
float:'float', double:'double',
Decimal:'decimal',
datetime.datetime:'dateTime', datetime.date:'date',
}
TYPE_MARSHAL_FN = {datetime.datetime:datetime_m, datetime.date:date_m,}
TYPE_UNMARSHAL_FN = {datetime.datetime:datetime_u, datetime.date:date_u,
bool:bool_u,
}
class OrderedDict(dict):
"Minimal ordered dictionary for xsd:sequences"
def __init__(self):
self.__keys = []
self.array = False
def __setitem__(self, key, value):
if key not in self.__keys:
self.__keys.append(key)
dict.__setitem__(self, key, value)
def insert(self, key, value, index=0):
if key not in self.__keys:
self.__keys.insert(index, key)
dict.__setitem__(self, key, value)
def __delitem__(self, key):
if key in self.__keys:
self.__keys.remove(key)
dict.__delitem__(self, key)
def __iter__(self):
return iter(self.__keys)
def keys(self):
return self.__keys
def items(self):
return [(key, self[key]) for key in self.__keys]
def update(self, other):
for k,v in other.items():
self[k] = v
if isinstance(other, OrderedDict):
self.array = other.array
def __str__(self):
return "*%s*" % dict.__str__(self)
def __repr__(self):
s= "*{%s}*" % ", ".join(['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
if self.array and False:
s = "[%s]" % s
return s
class SimpleXMLElement(object):
"Simple XML manipulation (simil PHP)"
def __init__(self, text = None, elements = None, document = None, namespace = None, prefix=None):
self.__ns = namespace
self.__prefix = prefix
if text:
try:
self.__document = xml.dom.minidom.parseString(text)
except:
if DEBUG: print text
raise
self.__elements = [self.__document.documentElement]
else:
self.__elements = elements
self.__document = document
def add_child(self,name,text=None,ns=True):
"Adding a child tag to a node"
if not ns or not self.__ns:
if DEBUG: print "adding %s" % (name)
element = self.__document.createElement(name)
else:
if DEBUG: print "adding %s ns %s %s" % (name, self.__ns,ns)
if self.__prefix:
element = self.__document.createElementNS(self.__ns, "%s:%s" % (self.__prefix, name))
else:
element = self.__document.createElementNS(self.__ns, name)
if text:
if isinstance(text, unicode):
element.appendChild(self.__document.createTextNode(text))
else:
element.appendChild(self.__document.createTextNode(str(text)))
self._element.appendChild(element)
return SimpleXMLElement(
elements=[element],
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix)
def __setattr__(self, tag, text):
"Add text child tag node (short form)"
if tag.startswith("_"):
object.__setattr__(self, tag, text)
else:
if DEBUG: print "__setattr__(%s,%s)" % (tag, text)
self.add_child(tag,text)
def add_comment(self, data):
"Add an xml comment to this child"
comment = self.__document.createComment(data)
self._element.appendChild(comment)
def as_xml(self,filename=None,pretty=False):
"Return the XML representation of the document"
if not pretty:
return self.__document.toxml('UTF-8')
else:
return self.__document.toprettyxml(encoding='UTF-8')
def __repr__(self):
"Return the XML representation of this tag"
return self._element.toxml('UTF-8')
def get_name(self):
"Return the tag name of this node"
return self._element.tagName
def get_local_name(self):
"Return the tag loca name (prefix:name) of this node"
return self._element.localName
def get_prefix(self):
"Return the namespace prefix of this node"
return self._element.prefix
def get_namespace_uri(self, ns):
"Return the namespace uri for a prefix"
v = self.__document.documentElement.attributes['xmlns:%s' % ns]
return v.value
def attributes(self):
"Return a dict of attributes for this tag"
#TODO: use slice syntax [:]?
return self._element.attributes
def __getitem__(self, item):
"Return xml tag attribute value or a slice of attributes (iter)"
if DEBUG: print "__getitem__(%s)" % item
if isinstance(item,basestring):
if self._element.hasAttribute(item):
return self._element.attributes[item].value
elif isinstance(item, slice):
# return a list with name:values
return self._element.attributes.items()[item]
else:
# return element by index (position)
element = self.__elements[item]
return SimpleXMLElement(
elements=[element],
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix)
def add_attribute(self, name, value):
"Set an attribute value from a string"
self._element.setAttribute(name, value)
def __setitem__(self, item, value):
"Set an attribute value"
if isinstance(item,basestring):
self.add_attribute(item, value)
elif isinstance(item, slice):
# set multiple attributes at once
for k, v in value.items():
self.add_attribute(k, v)
def __call__(self, tag=None, ns=None, children=False, error=True):
"Search (even in child nodes) and return a child tag by name"
try:
if tag is None:
# if no name given, iterate over siblings (same level)
return self.__iter__()
if children:
# future: filter children? by ns?
return self.children()
elements = None
if isinstance(tag, int):
# return tag by index
elements=[self.__elements[tag]]
if ns and not elements:
for ns_uri in isinstance(ns, (tuple, list)) and ns or (ns, ):
if DEBUG: print "searching %s by ns=%s" % (tag,ns_uri)
elements = self._element.getElementsByTagNameNS(ns_uri, tag)
if elements:
break
if self.__ns and not elements:
if DEBUG: print "searching %s by ns=%s" % (tag, self.__ns)
elements = self._element.getElementsByTagNameNS(self.__ns, tag)
if not elements:
if DEBUG: print "searching %s " % (tag)
elements = self._element.getElementsByTagName(tag)
if not elements:
if DEBUG: print self._element.toxml()
if error:
raise AttributeError("No elements found")
else:
return
return SimpleXMLElement(
elements=elements,
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix)
except AttributeError, e:
raise AttributeError("Tag not found: %s (%s)" % (tag, str(e)))
def __getattr__(self, tag):
"Shortcut for __call__"
return self.__call__(tag)
def __iter__(self):
"Iterate over xml tags at this level"
try:
for __element in self.__elements:
yield SimpleXMLElement(
elements=[__element],
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix)
except:
raise
def __dir__(self):
"List xml children tags names"
return [node.tagName for node
in self._element.childNodes
if node.nodeType != node.TEXT_NODE]
def children(self):
"Return xml children tags element"
elements=[__element for __element in self._element.childNodes
if __element.nodeType == __element.ELEMENT_NODE]
if not elements:
return None
#raise IndexError("Tag %s has no children" % self._element.tagName)
return SimpleXMLElement(
elements=elements,
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix)
def __len__(self):
"Return elements count"
return len(self.__elements)
def __contains__( self, item):
"Search for a tag name in this element or child nodes"
return self._element.getElementsByTagName(item)
def __unicode__(self):
"Returns the unicode text nodes of the current element"
if self._element.childNodes:
rc = u""
for node in self._element.childNodes:
if node.nodeType == node.TEXT_NODE:
rc = rc + node.data
return rc
return ''
def __str__(self):
"Returns the str text nodes of the current element"
return unicode(self).encode("utf8","ignore")
def __int__(self):
"Returns the integer value of the current element"
return int(self.__str__())
def __float__(self):
"Returns the float value of the current element"
try:
return float(self.__str__())
except:
raise IndexError(self._element.toxml())
_element = property(lambda self: self.__elements[0])
def unmarshall(self, types):
"Convert to python values the current serialized xml element"
# types is a dict of {tag name: convertion function}
# example: types={'p': {'a': int,'b': int}, 'c': [{'d':str}]}
# expected xml: <p><a>1</a><b>2</b></p><c><d>hola</d><d>chau</d>
# returnde value: {'p': {'a':1,'b':2}, `'c':[{'d':'hola'},{'d':'chau'}]}
d = {}
for node in self():
name = str(node.get_local_name())
try:
fn = types[name]
except (KeyError, ), e:
raise TypeError("Tag: %s invalid" % (name,))
if isinstance(fn,list):
value = []
children = node.children()
for child in children and children() or []:
value.append(child.unmarshall(fn[0]))
elif isinstance(fn,dict):
children = node.children()
value = children and children.unmarshall(fn)
else:
if fn is None: # xsd:anyType not unmarshalled
value = node
elif str(node) or fn == str:
try:
# get special desserialization function (if any)
fn = TYPE_UNMARSHAL_FN.get(fn,fn)
value = fn(unicode(node))
except (ValueError, TypeError), e:
raise ValueError("Tag: %s: %s" % (name, unicode(e)))
else:
value = None
d[name] = value
return d
def marshall(self, name, value, add_child=True, add_comments=False, ns=False):
"Analize python value and add the serialized XML element using tag name"
if isinstance(value, dict): # serialize dict (<key>value</key>)
child = add_child and self.add_child(name,ns=ns) or self
for k,v in value.items():
child.marshall(k, v, add_comments=add_comments, ns=ns)
elif isinstance(value, tuple): # serialize tuple (<key>value</key>)
child = add_child and self.add_child(name,ns=ns) or self
for k,v in value:
getattr(self,name).marshall(k, v, add_comments=add_comments, ns=ns)
elif isinstance(value, list): # serialize lists
child=self.add_child(name,ns=ns)
if add_comments:
child.add_comment("Repetitive array of:")
for t in value:
child.marshall(name,t, False, add_comments=add_comments, ns=ns)
elif isinstance(value, basestring): # do not convert strings or unicodes
self.add_child(name,value,ns=ns)
elif value is None: # sent a empty tag?
self.add_child(name,ns=ns)
elif value in TYPE_MAP.keys():
# add commented placeholders for simple tipes (for examples/help only)
child = self.add_child(name,ns=ns)
child.add_comment(TYPE_MAP[value])
else: # the rest of object types are converted to string
# get special serialization function (if any)
fn = TYPE_MARSHAL_FN.get(type(value),str)
self.add_child(name,fn(value),ns=ns)
def import_node(self, other):
x = self.__document.importNode(other._element, True) # deep copy
self._element.appendChild(x)
if __name__ == "__main__":
span = SimpleXMLElement('<span><a href="python.org.ar">pyar</a><prueba><i>1</i><float>1.5</float></prueba></span>')
assert str(span.a)==str(span('a'))==str(span.a(0))=="pyar"
assert span.a['href']=="python.org.ar"
assert int(span.prueba.i)==1 and float(span.prueba.float)==1.5
span1 = SimpleXMLElement('<span><a href="google.com">google</a><a>yahoo</a><a>hotmail</a></span>')
assert [str(a) for a in span1.a()] == ['google', 'yahoo', 'hotmail']
span1.add_child('a','altavista')
span1.b = "ex msn"
d = {'href':'http://www.bing.com/', 'alt': 'Bing'}
span1.b[:] = d
assert sorted([(k,v) for k,v in span1.b[:]]) == sorted(d.items())
print span1.as_xml()
assert 'b' in span1
span.import_node(span1)
print span.as_xml()
+126
View File
@@ -0,0 +1,126 @@
"""
Developed by 616d41631bff906704951934ffe4015e
Released under web2py license because includes gluon/cache.py source code
"""
import redis
from gluon import current
from gluon.cache import CacheAbstract
import cPickle as pickle
import time
import re
import thread
locker = thread.allocate_lock()
def RedisCache(*args, **vars):
"""
Usage example: put in models
from gluon.contrib.redis import RedisCache
cache.redis = RedisCache('localhost:6379',db=None, debug=True)
cache.redis.stats()
return a dictionary with statistics of Redis server
with one additional key ('w2p_keys') showing all keys currently set
from web2py with their TTL
if debug=True additional tracking is activate and another key is added
('w2p_stats') showing total_hits and misses
"""
locker.acquire()
try:
if not hasattr(RedisCache, 'redis_instance'):
RedisCache.redis_instance = RedisClient(*args, **vars)
finally:
locker.release()
return RedisCache.redis_instance
class RedisClient(object):
meta_storage = {}
def __init__(self, server='localhost:6379', db=None, debug=False):
host,port = (address.split(':')+['6379'])[:2]
port = int(port)
self.request=current.request
self.debug = debug
if request:
app = request.application
else:
app = ''
if not app in self.meta_storage:
self.storage = self.meta_storage[app] = {
CacheAbstract.cache_stats_name: {
'hit_total': 0,
'misses': 0,
}}
else:
self.storage = self.meta_storage[app]
self.r_server = redis.Redis(host=host, port=port, db=db or 0)
def __call__(self, key, f, time_expire=300):
if time_expire == None:
time_expire = 10**10
key = self.__keyFormat__(key)
value = None
obj = self.r_server.get(key)
if obj:
if self.debug:
self.r_server.incr('web2py_cache_statistics:hit_total')
value = pickle.loads(obj)
elif f is None:
if obj: self.r_server.delete(key)
else:
if self.debug:
self.r_server.incr('web2py_cache_statistics:misses')
value = f()
self.r_server.setex(key, pickle.dumps(value), time_expire)
return value
def increment(self, key, value=1, time_expire=300):
newKey = self.__keyFormat__(key)
obj = self.r_server.get(newKey)
if obj:
return self.r_server.incr(newKey, value)
else:
self.r_server.setex(newKey, value, time_expire)
return value
def clear(self, regex):
"""
Auxiliary function called by `clear` to search and
clear cache entries
"""
r = re.compile(regex)
prefix = "w2p:%s:" % (self.request.application)
pipe = self.r_server.pipeline()
for a in self.r_server.keys("%s*" % \
(prefix)):
if r.match(str(a).replace(prefix, '', 1)):
pipe.delete(a)
pipe.execute()
def stats(self):
statscollector = self.r_server.info()
if self.debug:
statscollector['w2p_stats'] = dict(
hit_total = self.r_server.get(
'web2py_cache_statistics:hit_total'),
misses=self.r_server.get('web2py_cache_statistics:misses')
)
statscollector['w2p_keys'] = dict()
for a in self.r_server.keys("w2p:%s:*" % (
self.request.application)):
statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = \
self.r_server.ttl(a)
return statscollector
def __keyFormat__(self, key):
return 'w2p:%s:%s' % (self.request.application,
key.replace(' ', '_'))
+590
View File
@@ -0,0 +1,590 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds."""
__name__ = 'PyRSS2Gen'
__version__ = (1, 0, 0)
__author__ = 'Andrew Dalke <dalke@dalkescientific.com>'
_generator_name = __name__ + '-' + '.'.join(map(str, __version__))
import datetime
import cStringIO
# Could make this the base class; will need to add 'publish'
class WriteXmlMixin:
def write_xml(self, outfile, encoding='iso-8859-1'):
from xml.sax import saxutils
handler = saxutils.XMLGenerator(outfile, encoding)
handler.startDocument()
self.publish(handler)
handler.endDocument()
def to_xml(self, encoding='iso-8859-1'):
try:
import cStringIO as StringIO
except ImportError:
import StringIO
f = StringIO.StringIO()
self.write_xml(f, encoding)
return f.getvalue()
def _element(
handler,
name,
obj,
d={},
):
if isinstance(obj, basestring) or obj is None:
# special-case handling to make the API easier
# to use for the common case.
handler.startElement(name, d)
if obj is not None:
handler.characters(obj)
handler.endElement(name)
else:
# It better know how to emit the correct XML.
obj.publish(handler)
def _opt_element(handler, name, obj):
if obj is None:
return
_element(handler, name, obj)
def _format_date(dt):
"""convert a datetime into an RFC 822 formatted date
Input date must be in GMT.
"""
# Looks like:
# Sat, 07 Sep 2002 00:00:01 GMT
# Can't use strftime because that's locale dependent
#
# Isn't there a standard way to do this for Python? The
# rfc822 and email.Utils modules assume a timestamp. The
# following is based on the rfc822 module.
return '%s, %02d %s %04d %02d:%02d:%02d GMT' % (
[
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat',
'Sun',
][dt.weekday()],
dt.day,
[
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
][dt.month - 1],
dt.year,
dt.hour,
dt.minute,
dt.second,
)
##
# A couple simple wrapper objects for the fields which
# take a simple value other than a string.
class IntElement:
"""implements the 'publish' API for integers
Takes the tag name and the integer value to publish.
(Could be used for anything which uses str() to be published
to text for XML.)
"""
element_attrs = {}
def __init__(self, name, val):
self.name = name
self.val = val
def publish(self, handler):
handler.startElement(self.name, self.element_attrs)
handler.characters(str(self.val))
handler.endElement(self.name)
class DateElement:
"""implements the 'publish' API for a datetime.datetime
Takes the tag name and the datetime to publish.
Converts the datetime to RFC 2822 timestamp (4-digit year).
"""
def __init__(self, name, dt):
self.name = name
self.dt = dt
def publish(self, handler):
_element(handler, self.name, _format_date(self.dt))
# ###
class Category:
"""Publish a category element"""
def __init__(self, category, domain=None):
self.category = category
self.domain = domain
def publish(self, handler):
d = {}
if self.domain is not None:
d['domain'] = self.domain
_element(handler, 'category', self.category, d)
class Cloud:
"""Publish a cloud"""
def __init__(
self,
domain,
port,
path,
registerProcedure,
protocol,
):
self.domain = domain
self.port = port
self.path = path
self.registerProcedure = registerProcedure
self.protocol = protocol
def publish(self, handler):
_element(handler, 'cloud', None, {
'domain': self.domain,
'port': str(self.port),
'path': self.path,
'registerProcedure': self.registerProcedure,
'protocol': self.protocol,
})
class Image:
"""Publish a channel Image"""
element_attrs = {}
def __init__(
self,
url,
title,
link,
width=None,
height=None,
description=None,
):
self.url = url
self.title = title
self.link = link
self.width = width
self.height = height
self.description = description
def publish(self, handler):
handler.startElement('image', self.element_attrs)
_element(handler, 'url', self.url)
_element(handler, 'title', self.title)
_element(handler, 'link', self.link)
width = self.width
if isinstance(width, int):
width = IntElement('width', width)
_opt_element(handler, 'width', width)
height = self.height
if isinstance(height, int):
height = IntElement('height', height)
_opt_element(handler, 'height', height)
_opt_element(handler, 'description', self.description)
handler.endElement('image')
class Guid:
"""Publish a guid
Defaults to being a permalink, which is the assumption if it's
omitted. Hence strings are always permalinks.
"""
def __init__(self, guid, isPermaLink=1):
self.guid = guid
self.isPermaLink = isPermaLink
def publish(self, handler):
d = {}
if self.isPermaLink:
d['isPermaLink'] = 'true'
else:
d['isPermaLink'] = 'false'
_element(handler, 'guid', self.guid, d)
class TextInput:
"""Publish a textInput
Apparently this is rarely used.
"""
element_attrs = {}
def __init__(
self,
title,
description,
name,
link,
):
self.title = title
self.description = description
self.name = name
self.link = link
def publish(self, handler):
handler.startElement('textInput', self.element_attrs)
_element(handler, 'title', self.title)
_element(handler, 'description', self.description)
_element(handler, 'name', self.name)
_element(handler, 'link', self.link)
handler.endElement('textInput')
class Enclosure:
"""Publish an enclosure"""
def __init__(
self,
url,
length,
type,
):
self.url = url
self.length = length
self.type = type
def publish(self, handler):
_element(handler, 'enclosure', None,
{'url': self.url, 'length': str(self.length), 'type': self.type})
class Source:
"""Publish the item's original source, used by aggregators"""
def __init__(self, name, url):
self.name = name
self.url = url
def publish(self, handler):
_element(handler, 'source', self.name, {'url': self.url})
class SkipHours:
"""Publish the skipHours
This takes a list of hours, as integers.
"""
element_attrs = {}
def __init__(self, hours):
self.hours = hours
def publish(self, handler):
if self.hours:
handler.startElement('skipHours', self.element_attrs)
for hour in self.hours:
_element(handler, 'hour', str(hour))
handler.endElement('skipHours')
class SkipDays:
"""Publish the skipDays
This takes a list of days as strings.
"""
element_attrs = {}
def __init__(self, days):
self.days = days
def publish(self, handler):
if self.days:
handler.startElement('skipDays', self.element_attrs)
for day in self.days:
_element(handler, 'day', day)
handler.endElement('skipDays')
class RSS2(WriteXmlMixin):
"""The main RSS class.
Stores the channel attributes, with the \"category\" elements under
\".categories\" and the RSS items under \".items\".
"""
rss_attrs = {'version': '2.0'}
element_attrs = {}
def __init__(
self,
title,
link,
description,
language=None,
copyright=None,
managingEditor=None,
webMaster=None,
pubDate=None,
lastBuildDate=None,
categories=None,
generator=_generator_name,
docs='http://blogs.law.harvard.edu/tech/rss',
cloud=None,
ttl=None,
image=None,
rating=None,
textInput=None,
skipHours=None,
skipDays=None,
items=None,
):
self.title = title
self.link = link
self.description = description
self.language = language
self.copyright = copyright
self.managingEditor = managingEditor
self.webMaster = webMaster
self.pubDate = pubDate
self.lastBuildDate = lastBuildDate
if categories is None:
categories = []
self.categories = categories
self.generator = generator
self.docs = docs
self.cloud = cloud
self.ttl = ttl
self.image = image
self.rating = rating
self.textInput = textInput
self.skipHours = skipHours
self.skipDays = skipDays
if items is None:
items = []
self.items = items
def publish(self, handler):
handler.startElement('rss', self.rss_attrs)
handler.startElement('channel', self.element_attrs)
_element(handler, 'title', self.title)
_element(handler, 'link', self.link)
_element(handler, 'description', self.description)
self.publish_extensions(handler)
_opt_element(handler, 'language', self.language)
_opt_element(handler, 'copyright', self.copyright)
_opt_element(handler, 'managingEditor', self.managingEditor)
_opt_element(handler, 'webMaster', self.webMaster)
pubDate = self.pubDate
if isinstance(pubDate, datetime.datetime):
pubDate = DateElement('pubDate', pubDate)
_opt_element(handler, 'pubDate', pubDate)
lastBuildDate = self.lastBuildDate
if isinstance(lastBuildDate, datetime.datetime):
lastBuildDate = DateElement('lastBuildDate', lastBuildDate)
_opt_element(handler, 'lastBuildDate', lastBuildDate)
for category in self.categories:
if isinstance(category, basestring):
category = Category(category)
category.publish(handler)
_opt_element(handler, 'generator', self.generator)
_opt_element(handler, 'docs', self.docs)
if self.cloud is not None:
self.cloud.publish(handler)
ttl = self.ttl
if isinstance(self.ttl, int):
ttl = IntElement('ttl', ttl)
_opt_element(handler, 'tt', ttl)
if self.image is not None:
self.image.publish(handler)
_opt_element(handler, 'rating', self.rating)
if self.textInput is not None:
self.textInput.publish(handler)
if self.skipHours is not None:
self.skipHours.publish(handler)
if self.skipDays is not None:
self.skipDays.publish(handler)
for item in self.items:
item.publish(handler)
handler.endElement('channel')
handler.endElement('rss')
def publish_extensions(self, handler):
# Derived classes can hook into this to insert
# output after the three required fields.
pass
class RSSItem(WriteXmlMixin):
"""Publish an RSS Item"""
element_attrs = {}
def __init__(
self,
title=None,
link=None,
description=None,
author=None,
categories=None,
comments=None,
enclosure=None,
guid=None,
pubDate=None,
source=None,
):
if title is None and description is None:
raise TypeError(
"RSSItem must define at least one of 'title' or 'description'")
self.title = title
self.link = link
self.description = description
self.author = author
if categories is None:
categories = []
self.categories = categories
self.comments = comments
self.enclosure = enclosure
self.guid = guid
self.pubDate = pubDate
self.source = source
# It sure does get tedious typing these names three times...
def publish(self, handler):
handler.startElement('item', self.element_attrs)
_opt_element(handler, 'title', self.title)
_opt_element(handler, 'link', self.link)
self.publish_extensions(handler)
_opt_element(handler, 'description', self.description)
_opt_element(handler, 'author', self.author)
for category in self.categories:
if isinstance(category, basestring):
category = Category(category)
category.publish(handler)
_opt_element(handler, 'comments', self.comments)
if self.enclosure is not None:
self.enclosure.publish(handler)
_opt_element(handler, 'guid', self.guid)
pubDate = self.pubDate
if isinstance(pubDate, datetime.datetime):
pubDate = DateElement('pubDate', pubDate)
_opt_element(handler, 'pubDate', pubDate)
if self.source is not None:
self.source.publish(handler)
handler.endElement('item')
def publish_extensions(self, handler):
# Derived classes can hook into this to insert
# output after the title and link elements
pass
def dumps(rss, encoding='utf-8'):
s = cStringIO.StringIO()
rss.write_xml(s, encoding)
return s.getvalue()
def test():
rss = RSS2(title='web2py feed', link='http://www.web2py.com',
description='About web2py',
lastBuildDate=datetime.datetime.now(),
items=[RSSItem(title='web2py and PyRSS2Gen-0.0',
link='http://www.web2py.com/examples/simple_examples/getrss',
description='web2py can now make rss feeds!',
guid=Guid('http://www.web2py.com/'),
pubDate=datetime.datetime(2007, 11, 14, 10, 30))])
return dumps(rss)
if __name__ == '__main__':
print test()
+268
View File
@@ -0,0 +1,268 @@
#!/usr/bin/python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Modified by Massimo Di Pierro so it works with and without GAE with web2py
# the modified version of this file is still released under the original Apache license
# and it is not released under the web2py license.
#
# This should be compatible with the Apache license since it states:
# "For the purposes of this License, Derivative Works shall not include works
# that remain separable from, or merely link (or bind by name) to the interfaces of,
# the Work and Derivative Works thereof."
#
# In fact this file is Apache-licensed and it is separable from the rest of web2py.
"""
An interactive, stateful AJAX shell that runs Python code on the server.
"""
import logging
import new
import os
import cPickle
import sys
import traceback
import types
import wsgiref.handlers
import StringIO
import threading
locker = threading.RLock()
# Set to True if stack traces should be shown in the browser, etc.
_DEBUG = True
# The entity kind for shell historys. Feel free to rename to suit your app.
_HISTORY_KIND = '_Shell_History'
# Types that can't be pickled.
UNPICKLABLE_TYPES = (
types.ModuleType,
types.TypeType,
types.ClassType,
types.FunctionType,
)
# Unpicklable statements to seed new historys with.
INITIAL_UNPICKLABLES = [
'import logging',
'import os',
'import sys',
]
class History:
"""A shell history. Stores the history's globals.
Each history globals is stored in one of two places:
If the global is picklable, it's stored in the parallel globals and
global_names list properties. (They're parallel lists to work around the
unfortunate fact that the datastore can't store dictionaries natively.)
If the global is not picklable (e.g. modules, classes, and functions), or if
it was created by the same statement that created an unpicklable global,
it's not stored directly. Instead, the statement is stored in the
unpicklables list property. On each request, before executing the current
statement, the unpicklable statements are evaluated to recreate the
unpicklable globals.
The unpicklable_names property stores all of the names of globals that were
added by unpicklable statements. When we pickle and store the globals after
executing a statement, we skip the ones in unpicklable_names.
Using Text instead of string is an optimization. We don't query on any of
these properties, so they don't need to be indexed.
"""
global_names = []
globals = []
unpicklable_names = []
unpicklables = []
def set_global(self, name, value):
"""Adds a global, or updates it if it already exists.
Also removes the global from the list of unpicklable names.
Args:
name: the name of the global to remove
value: any picklable value
"""
blob = cPickle.dumps(value)
if name in self.global_names:
index = self.global_names.index(name)
self.globals[index] = blob
else:
self.global_names.append(name)
self.globals.append(blob)
self.remove_unpicklable_name(name)
def remove_global(self, name):
"""Removes a global, if it exists.
Args:
name: string, the name of the global to remove
"""
if name in self.global_names:
index = self.global_names.index(name)
del self.global_names[index]
del self.globals[index]
def globals_dict(self):
"""Returns a dictionary view of the globals.
"""
return dict((name, cPickle.loads(val))
for name, val in zip(self.global_names, self.globals))
def add_unpicklable(self, statement, names):
"""Adds a statement and list of names to the unpicklables.
Also removes the names from the globals.
Args:
statement: string, the statement that created new unpicklable global(s).
names: list of strings; the names of the globals created by the statement.
"""
self.unpicklables.append(statement)
for name in names:
self.remove_global(name)
if name not in self.unpicklable_names:
self.unpicklable_names.append(name)
def remove_unpicklable_name(self, name):
"""Removes a name from the list of unpicklable names, if it exists.
Args:
name: string, the name of the unpicklable global to remove
"""
if name in self.unpicklable_names:
self.unpicklable_names.remove(name)
def represent(obj):
"""Returns a string representing the given object's value, which should allow the
code below to determine whether the object changes over time.
"""
try:
return cPickle.dumps(obj)
except:
return repr(obj)
def run(history, statement, env={}):
"""
Evaluates a python statement in a given history and returns the result.
"""
history.unpicklables = INITIAL_UNPICKLABLES
# extract the statement to be run
if not statement:
return ''
# the python compiler doesn't like network line endings
statement = statement.replace('\r\n', '\n')
# add a couple newlines at the end of the statement. this makes
# single-line expressions such as 'class Foo: pass' evaluate happily.
statement += '\n\n'
# log and compile the statement up front
try:
logging.info('Compiling and evaluating:\n%s' % statement)
compiled = compile(statement, '<string>', 'single')
except:
return str(traceback.format_exc())
# create a dedicated module to be used as this statement's __main__
statement_module = new.module('__main__')
# use this request's __builtin__, since it changes on each request.
# this is needed for import statements, among other things.
import __builtin__
statement_module.__builtins__ = __builtin__
# load the history from the datastore
history = History()
# swap in our custom module for __main__. then unpickle the history
# globals, run the statement, and re-pickle the history globals, all
# inside it.
old_main = sys.modules.get('__main__')
output = StringIO.StringIO()
try:
sys.modules['__main__'] = statement_module
statement_module.__name__ = '__main__'
statement_module.__dict__.update(env)
# re-evaluate the unpicklables
for code in history.unpicklables:
exec code in statement_module.__dict__
# re-initialize the globals
for name, val in history.globals_dict().items():
try:
statement_module.__dict__[name] = val
except:
msg = 'Dropping %s since it could not be unpickled.\n' % name
output.write(msg)
logging.warning(msg + traceback.format_exc())
history.remove_global(name)
# run!
old_globals = dict((key,represent(value)) for key,value in statement_module.__dict__.items())
try:
old_stdout, old_stderr = sys.stdout, sys.stderr
try:
sys.stderr = sys.stdout = output
locker.acquire()
exec compiled in statement_module.__dict__
finally:
locker.release()
sys.stdout, sys.stderr = old_stdout, old_stderr
except:
output.write(str(traceback.format_exc()))
return output.getvalue()
# extract the new globals that this statement added
new_globals = {}
for name, val in statement_module.__dict__.items():
if name not in old_globals or represent(val) != old_globals[name]:
new_globals[name] = val
if True in [isinstance(val, UNPICKLABLE_TYPES)
for val in new_globals.values()]:
# this statement added an unpicklable global. store the statement and
# the names of all of the globals it added in the unpicklables.
history.add_unpicklable(statement, new_globals.keys())
logging.debug('Storing this statement as an unpicklable.')
else:
# this statement didn't add any unpicklables. pickle and store the
# new globals back into the datastore.
for name, val in new_globals.items():
if not name.startswith('__'):
history.set_global(name, val)
finally:
sys.modules['__main__'] = old_main
return output.getvalue()
if __name__=='__main__':
history=History()
while True: print run(history, raw_input('>>> ')).rstrip()
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2006 Bob Ippolito
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+440
View File
@@ -0,0 +1,440 @@
r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
interchange format.
:mod:`simplejson` exposes an API familiar to users of the standard library
:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
version of the :mod:`json` library contained in Python 2.6, but maintains
compatibility with Python 2.4 and Python 2.5 and (currently) has
significant performance advantages, even without using the optional C
extension for speedups.
Encoding basic Python object hierarchies::
>>> import simplejson as json
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> print json.dumps("\"foo\bar")
"\"foo\bar"
>>> print json.dumps(u'\u1234')
"\u1234"
>>> print json.dumps('\\')
"\\"
>>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
{"a": 0, "b": 0, "c": 0}
>>> from StringIO import StringIO
>>> io = StringIO()
>>> json.dump(['streaming API'], io)
>>> io.getvalue()
'["streaming API"]'
Compact encoding::
>>> import simplejson as json
>>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
'[1,2,3,{"4":5,"6":7}]'
Pretty printing::
>>> import simplejson as json
>>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ')
>>> print '\n'.join([l.rstrip() for l in s.splitlines()])
{
"4": 5,
"6": 7
}
Decoding JSON::
>>> import simplejson as json
>>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
True
>>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
True
>>> from StringIO import StringIO
>>> io = StringIO('["streaming API"]')
>>> json.load(io)[0] == 'streaming API'
True
Specializing JSON object decoding::
>>> import simplejson as json
>>> def as_complex(dct):
... if '__complex__' in dct:
... return complex(dct['real'], dct['imag'])
... return dct
...
>>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
... object_hook=as_complex)
(1+2j)
>>> from decimal import Decimal
>>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
True
Specializing JSON object encoding::
>>> import simplejson as json
>>> def encode_complex(obj):
... if isinstance(obj, complex):
... return [obj.real, obj.imag]
... raise TypeError(repr(o) + " is not JSON serializable")
...
>>> json.dumps(2 + 1j, default=encode_complex)
'[2.0, 1.0]'
>>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
'[2.0, 1.0]'
>>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
'[2.0, 1.0]'
Using simplejson.tool from the shell to validate and pretty-print::
$ echo '{"json":"obj"}' | python -m simplejson.tool
{
"json": "obj"
}
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
Expecting property name: line 1 column 2 (char 2)
"""
__version__ = '2.1.3'
__all__ = [
'dump', 'dumps', 'load', 'loads',
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
'OrderedDict',
]
__author__ = 'Bob Ippolito <bob@redivi.com>'
from decimal import Decimal
from decoder import JSONDecoder, JSONDecodeError
from encoder import JSONEncoder
def _import_OrderedDict():
import collections
try:
return collections.OrderedDict
except AttributeError:
import ordered_dict
return ordered_dict.OrderedDict
OrderedDict = _import_OrderedDict()
def _import_c_make_encoder():
try:
raise ImportError # because assumes simplejson in path
from simplejson._speedups import make_encoder
return make_encoder
except ImportError:
return None
_default_encoder = JSONEncoder(
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
indent=None,
separators=None,
encoding='utf-8',
default=None,
use_decimal=False,
)
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=False, **kw):
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
``.write()``-supporting file-like object).
If ``skipkeys`` is true then ``dict`` keys that are not basic types
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
will be skipped instead of raising a ``TypeError``.
If ``ensure_ascii`` is false, then the some chunks written to ``fp``
may be ``unicode`` instances, subject to normal Python ``str`` to
``unicode`` coercion rules. Unless ``fp.write()`` explicitly
understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
to cause an error.
If ``check_circular`` is false, then the circular reference check
for container types will be skipped and a circular reference will
result in an ``OverflowError`` (or worse).
If ``allow_nan`` is false, then it will be a ``ValueError`` to
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
in strict compliance of the JSON specification, instead of using the
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
If *indent* is a string, then JSON array elements and object members
will be pretty-printed with a newline followed by that string repeated
for each level of nesting. ``None`` (the default) selects the most compact
representation without any newlines. For backwards compatibility with
versions of simplejson earlier than 2.1.0, an integer is also accepted
and is converted to a string with that many spaces.
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
then it will be used instead of the default ``(', ', ': ')`` separators.
``(',', ':')`` is the most compact JSON representation.
``encoding`` is the character encoding for str instances, default is UTF-8.
``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
If *use_decimal* is true (default: ``False``) then decimal.Decimal
will be natively serialized to JSON with full precision.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with
the ``cls`` kwarg.
"""
# cached encoder
if (not skipkeys and ensure_ascii and
check_circular and allow_nan and
cls is None and indent is None and separators is None and
encoding == 'utf-8' and default is None and not use_decimal
and not kw):
iterable = _default_encoder.iterencode(obj)
else:
if cls is None:
cls = JSONEncoder
iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
separators=separators, encoding=encoding,
default=default, use_decimal=use_decimal, **kw).iterencode(obj)
# could accelerate with writelines in some versions of Python, at
# a debuggability cost
for chunk in iterable:
fp.write(chunk)
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=False, **kw):
"""Serialize ``obj`` to a JSON formatted ``str``.
If ``skipkeys`` is false then ``dict`` keys that are not basic types
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
will be skipped instead of raising a ``TypeError``.
If ``ensure_ascii`` is false, then the return value will be a
``unicode`` instance subject to normal Python ``str`` to ``unicode``
coercion rules instead of being escaped to an ASCII ``str``.
If ``check_circular`` is false, then the circular reference check
for container types will be skipped and a circular reference will
result in an ``OverflowError`` (or worse).
If ``allow_nan`` is false, then it will be a ``ValueError`` to
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
strict compliance of the JSON specification, instead of using the
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
If ``indent`` is a string, then JSON array elements and object members
will be pretty-printed with a newline followed by that string repeated
for each level of nesting. ``None`` (the default) selects the most compact
representation without any newlines. For backwards compatibility with
versions of simplejson earlier than 2.1.0, an integer is also accepted
and is converted to a string with that many spaces.
If ``separators`` is an ``(item_separator, dict_separator)`` tuple
then it will be used instead of the default ``(', ', ': ')`` separators.
``(',', ':')`` is the most compact JSON representation.
``encoding`` is the character encoding for str instances, default is UTF-8.
``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
If *use_decimal* is true (default: ``False``) then decimal.Decimal
will be natively serialized to JSON with full precision.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with
the ``cls`` kwarg.
"""
# cached encoder
if (not skipkeys and ensure_ascii and
check_circular and allow_nan and
cls is None and indent is None and separators is None and
encoding == 'utf-8' and default is None and not use_decimal
and not kw):
return _default_encoder.encode(obj)
if cls is None:
cls = JSONEncoder
return cls(
skipkeys=skipkeys, ensure_ascii=ensure_ascii,
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
separators=separators, encoding=encoding, default=default,
use_decimal=use_decimal, **kw).encode(obj)
_default_decoder = JSONDecoder(encoding=None, object_hook=None,
object_pairs_hook=None)
def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None,
use_decimal=False, **kw):
"""Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
a JSON document) to a Python object.
*encoding* determines the encoding used to interpret any
:class:`str` objects decoded by this instance (``'utf-8'`` by
default). It has no effect when decoding :class:`unicode` objects.
Note that currently only encodings that are a superset of ASCII work,
strings of other encodings should be passed in as :class:`unicode`.
*object_hook*, if specified, will be called with the result of every
JSON object decoded and its return value will be used in place of the
given :class:`dict`. This can be used to provide custom
deserializations (e.g. to support JSON-RPC class hinting).
*object_pairs_hook* is an optional function that will be called with
the result of any object literal decode with an ordered list of pairs.
The return value of *object_pairs_hook* will be used instead of the
:class:`dict`. This feature can be used to implement custom decoders
that rely on the order that the key and value pairs are decoded (for
example, :func:`collections.OrderedDict` will remember the order of
insertion). If *object_hook* is also defined, the *object_pairs_hook*
takes priority.
*parse_float*, if specified, will be called with the string of every
JSON float to be decoded. By default, this is equivalent to
``float(num_str)``. This can be used to use another datatype or parser
for JSON floats (e.g. :class:`decimal.Decimal`).
*parse_int*, if specified, will be called with the string of every
JSON int to be decoded. By default, this is equivalent to
``int(num_str)``. This can be used to use another datatype or parser
for JSON integers (e.g. :class:`float`).
*parse_constant*, if specified, will be called with one of the
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
can be used to raise an exception if invalid JSON numbers are
encountered.
If *use_decimal* is true (default: ``False``) then it implies
parse_float=decimal.Decimal for parity with ``dump``.
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
kwarg.
"""
return loads(fp.read(),
encoding=encoding, cls=cls, object_hook=object_hook,
parse_float=parse_float, parse_int=parse_int,
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
use_decimal=use_decimal, **kw)
def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None,
use_decimal=False, **kw):
"""Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
document) to a Python object.
*encoding* determines the encoding used to interpret any
:class:`str` objects decoded by this instance (``'utf-8'`` by
default). It has no effect when decoding :class:`unicode` objects.
Note that currently only encodings that are a superset of ASCII work,
strings of other encodings should be passed in as :class:`unicode`.
*object_hook*, if specified, will be called with the result of every
JSON object decoded and its return value will be used in place of the
given :class:`dict`. This can be used to provide custom
deserializations (e.g. to support JSON-RPC class hinting).
*object_pairs_hook* is an optional function that will be called with
the result of any object literal decode with an ordered list of pairs.
The return value of *object_pairs_hook* will be used instead of the
:class:`dict`. This feature can be used to implement custom decoders
that rely on the order that the key and value pairs are decoded (for
example, :func:`collections.OrderedDict` will remember the order of
insertion). If *object_hook* is also defined, the *object_pairs_hook*
takes priority.
*parse_float*, if specified, will be called with the string of every
JSON float to be decoded. By default, this is equivalent to
``float(num_str)``. This can be used to use another datatype or parser
for JSON floats (e.g. :class:`decimal.Decimal`).
*parse_int*, if specified, will be called with the string of every
JSON int to be decoded. By default, this is equivalent to
``int(num_str)``. This can be used to use another datatype or parser
for JSON integers (e.g. :class:`float`).
*parse_constant*, if specified, will be called with one of the
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
can be used to raise an exception if invalid JSON numbers are
encountered.
If *use_decimal* is true (default: ``False``) then it implies
parse_float=decimal.Decimal for parity with ``dump``.
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
kwarg.
"""
if (cls is None and encoding is None and object_hook is None and
parse_int is None and parse_float is None and
parse_constant is None and object_pairs_hook is None
and not use_decimal and not kw):
return _default_decoder.decode(s)
if cls is None:
cls = JSONDecoder
if object_hook is not None:
kw['object_hook'] = object_hook
if object_pairs_hook is not None:
kw['object_pairs_hook'] = object_pairs_hook
if parse_float is not None:
kw['parse_float'] = parse_float
if parse_int is not None:
kw['parse_int'] = parse_int
if parse_constant is not None:
kw['parse_constant'] = parse_constant
if use_decimal:
if parse_float is not None:
raise TypeError("use_decimal=True implies parse_float=Decimal")
kw['parse_float'] = Decimal
return cls(encoding=encoding, **kw).decode(s)
def _toggle_speedups(enabled):
import decoder as dec
import encoder as enc
import scanner as scan
c_make_encoder = _import_c_make_encoder()
if enabled:
dec.scanstring = dec.c_scanstring or dec.py_scanstring
enc.c_make_encoder = c_make_encoder
enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
enc.py_encode_basestring_ascii)
scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
else:
dec.scanstring = dec.py_scanstring
enc.c_make_encoder = None
enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
scan.make_scanner = scan.py_make_scanner
dec.make_scanner = scan.make_scanner
global _default_decoder
_default_decoder = JSONDecoder(
encoding=None,
object_hook=None,
object_pairs_hook=None,
)
global _default_encoder
_default_encoder = JSONEncoder(
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
indent=None,
separators=None,
encoding='utf-8',
default=None,
)
+423
View File
@@ -0,0 +1,423 @@
"""Implementation of JSONDecoder
"""
import re
import sys
import struct
from scanner import make_scanner
def _import_c_scanstring():
try:
raise ImportError # because assumes simplejson in path
from simplejson._speedups import scanstring
return scanstring
except ImportError:
return None
c_scanstring = _import_c_scanstring()
__all__ = ['JSONDecoder']
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
def _floatconstants():
_BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
# The struct module in Python 2.4 would get frexp() out of range here
# when an endian is specified in the format string. Fixed in Python 2.5+
if sys.byteorder != 'big':
_BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
nan, inf = struct.unpack('dd', _BYTES)
return nan, inf, -inf
NaN, PosInf, NegInf = _floatconstants()
class JSONDecodeError(ValueError):
"""Subclass of ValueError with the following additional properties:
msg: The unformatted error message
doc: The JSON document being parsed
pos: The start index of doc where parsing failed
end: The end index of doc where parsing failed (may be None)
lineno: The line corresponding to pos
colno: The column corresponding to pos
endlineno: The line corresponding to end (may be None)
endcolno: The column corresponding to end (may be None)
"""
def __init__(self, msg, doc, pos, end=None):
ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
self.msg = msg
self.doc = doc
self.pos = pos
self.end = end
self.lineno, self.colno = linecol(doc, pos)
if end is not None:
self.endlineno, self.endcolno = linecol(doc, end)
else:
self.endlineno, self.endcolno = None, None
def linecol(doc, pos):
lineno = doc.count('\n', 0, pos) + 1
if lineno == 1:
colno = pos
else:
colno = pos - doc.rindex('\n', 0, pos)
return lineno, colno
def errmsg(msg, doc, pos, end=None):
# Note that this function is called from _speedups
lineno, colno = linecol(doc, pos)
if end is None:
#fmt = '{0}: line {1} column {2} (char {3})'
#return fmt.format(msg, lineno, colno, pos)
fmt = '%s: line %d column %d (char %d)'
return fmt % (msg, lineno, colno, pos)
endlineno, endcolno = linecol(doc, end)
#fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
#return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
_CONSTANTS = {
'-Infinity': NegInf,
'Infinity': PosInf,
'NaN': NaN,
}
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
BACKSLASH = {
'"': u'"', '\\': u'\\', '/': u'/',
'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
}
DEFAULT_ENCODING = "utf-8"
def py_scanstring(s, end, encoding=None, strict=True,
_b=BACKSLASH, _m=STRINGCHUNK.match):
"""Scan the string s for a JSON string. End is the index of the
character in s after the quote that started the JSON string.
Unescapes all valid JSON string escape sequences and raises ValueError
on attempt to decode an invalid string. If strict is False then literal
control characters are allowed in the string.
Returns a tuple of the decoded string and the index of the character in s
after the end quote."""
if encoding is None:
encoding = DEFAULT_ENCODING
chunks = []
_append = chunks.append
begin = end - 1
while 1:
chunk = _m(s, end)
if chunk is None:
raise JSONDecodeError(
"Unterminated string starting at", s, begin)
end = chunk.end()
content, terminator = chunk.groups()
# Content is contains zero or more unescaped string characters
if content:
if not isinstance(content, unicode):
content = unicode(content, encoding)
_append(content)
# Terminator is the end of string, a literal control character,
# or a backslash denoting that an escape sequence follows
if terminator == '"':
break
elif terminator != '\\':
if strict:
msg = "Invalid control character %r at" % (terminator,)
#msg = "Invalid control character {0!r} at".format(terminator)
raise JSONDecodeError(msg, s, end)
else:
_append(terminator)
continue
try:
esc = s[end]
except IndexError:
raise JSONDecodeError(
"Unterminated string starting at", s, begin)
# If not a unicode escape sequence, must be in the lookup table
if esc != 'u':
try:
char = _b[esc]
except KeyError:
msg = "Invalid \\escape: " + repr(esc)
raise JSONDecodeError(msg, s, end)
end += 1
else:
# Unicode escape sequence
esc = s[end + 1:end + 5]
next_end = end + 5
if len(esc) != 4:
msg = "Invalid \\uXXXX escape"
raise JSONDecodeError(msg, s, end)
uni = int(esc, 16)
# Check for surrogate pair on UCS-4 systems
if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
if not s[end + 5:end + 7] == '\\u':
raise JSONDecodeError(msg, s, end)
esc2 = s[end + 7:end + 11]
if len(esc2) != 4:
raise JSONDecodeError(msg, s, end)
uni2 = int(esc2, 16)
uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
next_end += 6
char = unichr(uni)
end = next_end
# Append the unescaped character
_append(char)
return u''.join(chunks), end
# Use speedup if available
scanstring = c_scanstring or py_scanstring
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
WHITESPACE_STR = ' \t\n\r'
def JSONObject((s, end), encoding, strict, scan_once, object_hook,
object_pairs_hook, memo=None,
_w=WHITESPACE.match, _ws=WHITESPACE_STR):
# Backwards compatibility
if memo is None:
memo = {}
memo_get = memo.setdefault
pairs = []
# Use a slice to prevent IndexError from being raised, the following
# check will raise a more specific ValueError if the string is empty
nextchar = s[end:end + 1]
# Normally we expect nextchar == '"'
if nextchar != '"':
if nextchar in _ws:
end = _w(s, end).end()
nextchar = s[end:end + 1]
# Trivial empty object
if nextchar == '}':
if object_pairs_hook is not None:
result = object_pairs_hook(pairs)
return result, end + 1
pairs = {}
if object_hook is not None:
pairs = object_hook(pairs)
return pairs, end + 1
elif nextchar != '"':
raise JSONDecodeError("Expecting property name", s, end)
end += 1
while True:
key, end = scanstring(s, end, encoding, strict)
key = memo_get(key, key)
# To skip some function call overhead we optimize the fast paths where
# the JSON key separator is ": " or just ":".
if s[end:end + 1] != ':':
end = _w(s, end).end()
if s[end:end + 1] != ':':
raise JSONDecodeError("Expecting : delimiter", s, end)
end += 1
try:
if s[end] in _ws:
end += 1
if s[end] in _ws:
end = _w(s, end + 1).end()
except IndexError:
pass
try:
value, end = scan_once(s, end)
except StopIteration:
raise JSONDecodeError("Expecting object", s, end)
pairs.append((key, value))
try:
nextchar = s[end]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end]
except IndexError:
nextchar = ''
end += 1
if nextchar == '}':
break
elif nextchar != ',':
raise JSONDecodeError("Expecting , delimiter", s, end - 1)
try:
nextchar = s[end]
if nextchar in _ws:
end += 1
nextchar = s[end]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end]
except IndexError:
nextchar = ''
end += 1
if nextchar != '"':
raise JSONDecodeError("Expecting property name", s, end - 1)
if object_pairs_hook is not None:
result = object_pairs_hook(pairs)
return result, end
pairs = dict(pairs)
if object_hook is not None:
pairs = object_hook(pairs)
return pairs, end
def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
values = []
nextchar = s[end:end + 1]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end:end + 1]
# Look-ahead for trivial empty array
if nextchar == ']':
return values, end + 1
_append = values.append
while True:
try:
value, end = scan_once(s, end)
except StopIteration:
raise JSONDecodeError("Expecting object", s, end)
_append(value)
nextchar = s[end:end + 1]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end:end + 1]
end += 1
if nextchar == ']':
break
elif nextchar != ',':
raise JSONDecodeError("Expecting , delimiter", s, end)
try:
if s[end] in _ws:
end += 1
if s[end] in _ws:
end = _w(s, end + 1).end()
except IndexError:
pass
return values, end
class JSONDecoder(object):
"""Simple JSON <http://json.org> decoder
Performs the following translations in decoding by default:
+---------------+-------------------+
| JSON | Python |
+===============+===================+
| object | dict |
+---------------+-------------------+
| array | list |
+---------------+-------------------+
| string | unicode |
+---------------+-------------------+
| number (int) | int, long |
+---------------+-------------------+
| number (real) | float |
+---------------+-------------------+
| true | True |
+---------------+-------------------+
| false | False |
+---------------+-------------------+
| null | None |
+---------------+-------------------+
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
their corresponding ``float`` values, which is outside the JSON spec.
"""
def __init__(self, encoding=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, strict=True,
object_pairs_hook=None):
"""
*encoding* determines the encoding used to interpret any
:class:`str` objects decoded by this instance (``'utf-8'`` by
default). It has no effect when decoding :class:`unicode` objects.
Note that currently only encodings that are a superset of ASCII work,
strings of other encodings should be passed in as :class:`unicode`.
*object_hook*, if specified, will be called with the result of every
JSON object decoded and its return value will be used in place of the
given :class:`dict`. This can be used to provide custom
deserializations (e.g. to support JSON-RPC class hinting).
*object_pairs_hook* is an optional function that will be called with
the result of any object literal decode with an ordered list of pairs.
The return value of *object_pairs_hook* will be used instead of the
:class:`dict`. This feature can be used to implement custom decoders
that rely on the order that the key and value pairs are decoded (for
example, :func:`collections.OrderedDict` will remember the order of
insertion). If *object_hook* is also defined, the *object_pairs_hook*
takes priority.
*parse_float*, if specified, will be called with the string of every
JSON float to be decoded. By default, this is equivalent to
``float(num_str)``. This can be used to use another datatype or parser
for JSON floats (e.g. :class:`decimal.Decimal`).
*parse_int*, if specified, will be called with the string of every
JSON int to be decoded. By default, this is equivalent to
``int(num_str)``. This can be used to use another datatype or parser
for JSON integers (e.g. :class:`float`).
*parse_constant*, if specified, will be called with one of the
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
can be used to raise an exception if invalid JSON numbers are
encountered.
*strict* controls the parser's behavior when it encounters an
invalid control character in a string. The default setting of
``True`` means that unescaped control characters are parse errors, if
``False`` then control characters will be allowed in strings.
"""
self.encoding = encoding
self.object_hook = object_hook
self.object_pairs_hook = object_pairs_hook
self.parse_float = parse_float or float
self.parse_int = parse_int or int
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
self.strict = strict
self.parse_object = JSONObject
self.parse_array = JSONArray
self.parse_string = scanstring
self.memo = {}
self.scan_once = make_scanner(self)
def decode(self, s, _w=WHITESPACE.match):
"""Return the Python representation of ``s`` (a ``str`` or ``unicode``
instance containing a JSON document)
"""
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
end = _w(s, end).end()
if end != len(s):
raise JSONDecodeError("Extra data", s, end, len(s))
return obj
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` or ``unicode``
beginning with a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration:
raise JSONDecodeError("No JSON object could be decoded", s, idx)
return obj, end
+503
View File
@@ -0,0 +1,503 @@
"""Implementation of JSONEncoder
"""
import re
from decimal import Decimal
def _import_speedups():
try:
raise ImportError # because assumes simplejson in path
from simplejson import _speedups
return _speedups.encode_basestring_ascii, _speedups.make_encoder
except ImportError:
return None, None
c_encode_basestring_ascii, c_make_encoder = _import_speedups()
from decoder import PosInf
ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
HAS_UTF8 = re.compile(r'[\x80-\xff]')
ESCAPE_DCT = {
'\\': '\\\\',
'"': '\\"',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
}
for i in range(0x20):
#ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
FLOAT_REPR = repr
def encode_basestring(s):
"""Return a JSON representation of a Python string
"""
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
s = s.decode('utf-8')
def replace(match):
return ESCAPE_DCT[match.group(0)]
return u'"' + ESCAPE.sub(replace, s) + u'"'
def py_encode_basestring_ascii(s):
"""Return an ASCII-only JSON representation of a Python string
"""
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
s = s.decode('utf-8')
def replace(match):
s = match.group(0)
try:
return ESCAPE_DCT[s]
except KeyError:
n = ord(s)
if n < 0x10000:
#return '\\u{0:04x}'.format(n)
return '\\u%04x' % (n,)
else:
# surrogate pair
n -= 0x10000
s1 = 0xd800 | ((n >> 10) & 0x3ff)
s2 = 0xdc00 | (n & 0x3ff)
#return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
return '\\u%04x\\u%04x' % (s1, s2)
return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
encode_basestring_ascii = (
c_encode_basestring_ascii or py_encode_basestring_ascii)
class JSONEncoder(object):
"""Extensible JSON <http://json.org> encoder for Python data structures.
Supports the following objects and types by default:
+-------------------+---------------+
| Python | JSON |
+===================+===============+
| dict | object |
+-------------------+---------------+
| list, tuple | array |
+-------------------+---------------+
| str, unicode | string |
+-------------------+---------------+
| int, long, float | number |
+-------------------+---------------+
| True | true |
+-------------------+---------------+
| False | false |
+-------------------+---------------+
| None | null |
+-------------------+---------------+
To extend this to recognize other objects, subclass and implement a
``.default()`` method with another method that returns a serializable
object for ``o`` if possible, otherwise it should call the superclass
implementation (to raise ``TypeError``).
"""
item_separator = ', '
key_separator = ': '
def __init__(self, skipkeys=False, ensure_ascii=True,
check_circular=True, allow_nan=True, sort_keys=False,
indent=None, separators=None, encoding='utf-8', default=None,
use_decimal=False):
"""Constructor for JSONEncoder, with sensible defaults.
If skipkeys is false, then it is a TypeError to attempt
encoding of keys that are not str, int, long, float or None. If
skipkeys is True, such items are simply skipped.
If ensure_ascii is true, the output is guaranteed to be str
objects with all incoming unicode characters escaped. If
ensure_ascii is false, the output will be unicode object.
If check_circular is true, then lists, dicts, and custom encoded
objects will be checked for circular references during encoding to
prevent an infinite recursion (which would cause an OverflowError).
Otherwise, no such check takes place.
If allow_nan is true, then NaN, Infinity, and -Infinity will be
encoded as such. This behavior is not JSON specification compliant,
but is consistent with most JavaScript based encoders and decoders.
Otherwise, it will be a ValueError to encode such floats.
If sort_keys is true, then the output of dictionaries will be
sorted by key; this is useful for regression tests to ensure
that JSON serializations can be compared on a day-to-day basis.
If indent is a string, then JSON array elements and object members
will be pretty-printed with a newline followed by that string repeated
for each level of nesting. ``None`` (the default) selects the most compact
representation without any newlines. For backwards compatibility with
versions of simplejson earlier than 2.1.0, an integer is also accepted
and is converted to a string with that many spaces.
If specified, separators should be a (item_separator, key_separator)
tuple. The default is (', ', ': '). To get the most compact JSON
representation you should specify (',', ':') to eliminate whitespace.
If specified, default is a function that gets called for objects
that can't otherwise be serialized. It should return a JSON encodable
version of the object or raise a ``TypeError``.
If encoding is not None, then all input strings will be
transformed into unicode using that encoding prior to JSON-encoding.
The default is UTF-8.
If use_decimal is true (not the default), ``decimal.Decimal`` will
be supported directly by the encoder. For the inverse, decode JSON
with ``parse_float=decimal.Decimal``.
"""
self.skipkeys = skipkeys
self.ensure_ascii = ensure_ascii
self.check_circular = check_circular
self.allow_nan = allow_nan
self.sort_keys = sort_keys
self.use_decimal = use_decimal
if isinstance(indent, (int, long)):
indent = ' ' * indent
self.indent = indent
if separators is not None:
self.item_separator, self.key_separator = separators
if default is not None:
self.default = default
self.encoding = encoding
def default(self, o):
"""Implement this method in a subclass such that it returns
a serializable object for ``o``, or calls the base implementation
(to raise a ``TypeError``).
For example, to support arbitrary iterators, you could
implement default like this::
def default(self, o):
try:
iterable = iter(o)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, o)
"""
raise TypeError(repr(o) + " is not JSON serializable")
def encode(self, o):
"""Return a JSON string representation of a Python data structure.
>>> from simplejson import JSONEncoder
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
'{"foo": ["bar", "baz"]}'
"""
# This is for extremely simple cases and benchmarks.
if isinstance(o, basestring):
if isinstance(o, str):
_encoding = self.encoding
if (_encoding is not None
and not (_encoding == 'utf-8')):
o = o.decode(_encoding)
if self.ensure_ascii:
return encode_basestring_ascii(o)
else:
return encode_basestring(o)
# This doesn't pass the iterator directly to ''.join() because the
# exceptions aren't as detailed. The list call should be roughly
# equivalent to the PySequence_Fast that ''.join() would do.
chunks = self.iterencode(o, _one_shot=True)
if not isinstance(chunks, (list, tuple)):
chunks = list(chunks)
if self.ensure_ascii:
return ''.join(chunks)
else:
return u''.join(chunks)
def iterencode(self, o, _one_shot=False):
"""Encode the given object and yield each string
representation as available.
For example::
for chunk in JSONEncoder().iterencode(bigobject):
mysocket.write(chunk)
"""
if self.check_circular:
markers = {}
else:
markers = None
if self.ensure_ascii:
_encoder = encode_basestring_ascii
else:
_encoder = encode_basestring
if self.encoding != 'utf-8':
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
if isinstance(o, str):
o = o.decode(_encoding)
return _orig_encoder(o)
def floatstr(o, allow_nan=self.allow_nan,
_repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
# Check for specials. Note that this type of test is processor
# and/or platform-specific, so do tests which don't depend on
# the internals.
if o != o:
text = 'NaN'
elif o == _inf:
text = 'Infinity'
elif o == _neginf:
text = '-Infinity'
else:
return _repr(o)
if not allow_nan:
raise ValueError(
"Out of range float values are not JSON compliant: " +
repr(o))
return text
key_memo = {}
if (_one_shot and c_make_encoder is not None
and self.indent is None):
_iterencode = c_make_encoder(
markers, self.default, _encoder, self.indent,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, self.allow_nan, key_memo, self.use_decimal)
else:
_iterencode = _make_iterencode(
markers, self.default, _encoder, self.indent, floatstr,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, _one_shot, self.use_decimal)
try:
return _iterencode(o, 0)
finally:
key_memo.clear()
class JSONEncoderForHTML(JSONEncoder):
"""An encoder that produces JSON safe to embed in HTML.
To embed JSON content in, say, a script tag on a web page, the
characters &, < and > should be escaped. They cannot be escaped
with the usual entities (e.g. &amp;) because they are not expanded
within <script> tags.
"""
def encode(self, o):
# Override JSONEncoder.encode because it has hacks for
# performance that make things more complicated.
chunks = self.iterencode(o, True)
if self.ensure_ascii:
return ''.join(chunks)
else:
return u''.join(chunks)
def iterencode(self, o, _one_shot=False):
chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot)
for chunk in chunks:
chunk = chunk.replace('&', '\\u0026')
chunk = chunk.replace('<', '\\u003c')
chunk = chunk.replace('>', '\\u003e')
yield chunk
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
_use_decimal,
## HACK: hand-optimized bytecode; turn globals into locals
False=False,
True=True,
ValueError=ValueError,
basestring=basestring,
Decimal=Decimal,
dict=dict,
float=float,
id=id,
int=int,
isinstance=isinstance,
list=list,
long=long,
str=str,
tuple=tuple,
):
def _iterencode_list(lst, _current_indent_level):
if not lst:
yield '[]'
return
if markers is not None:
markerid = id(lst)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = lst
buf = '['
if _indent is not None:
_current_indent_level += 1
newline_indent = '\n' + (_indent * _current_indent_level)
separator = _item_separator + newline_indent
buf += newline_indent
else:
newline_indent = None
separator = _item_separator
first = True
for value in lst:
if first:
first = False
else:
buf = separator
if isinstance(value, basestring):
yield buf + _encoder(value)
elif value is None:
yield buf + 'null'
elif value is True:
yield buf + 'true'
elif value is False:
yield buf + 'false'
elif isinstance(value, (int, long)):
yield buf + str(value)
elif isinstance(value, float):
yield buf + _floatstr(value)
elif _use_decimal and isinstance(value, Decimal):
yield buf + str(value)
else:
yield buf
if isinstance(value, (list, tuple)):
chunks = _iterencode_list(value, _current_indent_level)
elif isinstance(value, dict):
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
for chunk in chunks:
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + (_indent * _current_indent_level)
yield ']'
if markers is not None:
del markers[markerid]
def _iterencode_dict(dct, _current_indent_level):
if not dct:
yield '{}'
return
if markers is not None:
markerid = id(dct)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = dct
yield '{'
if _indent is not None:
_current_indent_level += 1
newline_indent = '\n' + (_indent * _current_indent_level)
item_separator = _item_separator + newline_indent
yield newline_indent
else:
newline_indent = None
item_separator = _item_separator
first = True
if _sort_keys:
items = dct.items()
items.sort(key=lambda kv: kv[0])
else:
items = dct.iteritems()
for key, value in items:
if isinstance(key, basestring):
pass
# JavaScript is weakly typed for these, so it makes sense to
# also allow them. Many encoders seem to do something like this.
elif isinstance(key, float):
key = _floatstr(key)
elif key is True:
key = 'true'
elif key is False:
key = 'false'
elif key is None:
key = 'null'
elif isinstance(key, (int, long)):
key = str(key)
elif _skipkeys:
continue
else:
raise TypeError("key " + repr(key) + " is not a string")
if first:
first = False
else:
yield item_separator
yield _encoder(key)
yield _key_separator
if isinstance(value, basestring):
yield _encoder(value)
elif value is None:
yield 'null'
elif value is True:
yield 'true'
elif value is False:
yield 'false'
elif isinstance(value, (int, long)):
yield str(value)
elif isinstance(value, float):
yield _floatstr(value)
elif _use_decimal and isinstance(value, Decimal):
yield str(value)
else:
if isinstance(value, (list, tuple)):
chunks = _iterencode_list(value, _current_indent_level)
elif isinstance(value, dict):
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
for chunk in chunks:
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + (_indent * _current_indent_level)
yield '}'
if markers is not None:
del markers[markerid]
def _iterencode(o, _current_indent_level):
if isinstance(o, basestring):
yield _encoder(o)
elif o is None:
yield 'null'
elif o is True:
yield 'true'
elif o is False:
yield 'false'
elif isinstance(o, (int, long)):
yield str(o)
elif isinstance(o, float):
yield _floatstr(o)
elif isinstance(o, (list, tuple)):
for chunk in _iterencode_list(o, _current_indent_level):
yield chunk
elif isinstance(o, dict):
for chunk in _iterencode_dict(o, _current_indent_level):
yield chunk
elif _use_decimal and isinstance(o, Decimal):
yield str(o)
else:
if markers is not None:
markerid = id(o)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = o
o = _default(o)
for chunk in _iterencode(o, _current_indent_level):
yield chunk
if markers is not None:
del markers[markerid]
return _iterencode
+120
View File
@@ -0,0 +1,120 @@
"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger
http://code.activestate.com/recipes/576693/
"""
from UserDict import DictMixin
# Modified from original to support Python 2.4, see
# http://code.google.com/p/simplejson/issues/detail?id=53
try:
all
except NameError:
def all(seq):
for elem in seq:
if not elem:
return False
return True
class OrderedDict(dict, DictMixin):
def __init__(self, *args, **kwds):
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__end
except AttributeError:
self.clear()
self.update(*args, **kwds)
def clear(self):
self.__end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.__map = {} # key --> [key, prev, next]
dict.clear(self)
def __setitem__(self, key, value):
if key not in self:
end = self.__end
curr = end[1]
curr[2] = end[1] = self.__map[key] = [key, curr, end]
dict.__setitem__(self, key, value)
def __delitem__(self, key):
dict.__delitem__(self, key)
key, prev, next = self.__map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.__end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.__end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def popitem(self, last=True):
if not self:
raise KeyError('dictionary is empty')
# Modified from original to support Python 2.4, see
# http://code.google.com/p/simplejson/issues/detail?id=53
if last:
key = reversed(self).next()
else:
key = iter(self).next()
value = self.pop(key)
return key, value
def __reduce__(self):
items = [[k, self[k]] for k in self]
tmp = self.__map, self.__end
del self.__map, self.__end
inst_dict = vars(self).copy()
self.__map, self.__end = tmp
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
def keys(self):
return list(self)
setdefault = DictMixin.setdefault
update = DictMixin.update
pop = DictMixin.pop
values = DictMixin.values
items = DictMixin.items
iterkeys = DictMixin.iterkeys
itervalues = DictMixin.itervalues
iteritems = DictMixin.iteritems
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, self.items())
def copy(self):
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
def __eq__(self, other):
if isinstance(other, OrderedDict):
return len(self)==len(other) and \
all(p==q for p, q in zip(self.items(), other.items()))
return dict.__eq__(self, other)
def __ne__(self, other):
return not self == other
+79
View File
@@ -0,0 +1,79 @@
"""JSON token scanner
"""
import re
def _import_c_make_scanner():
try:
raise ImportError # because assumes simplejson in path
from simplejson._speedups import make_scanner
return make_scanner
except ImportError:
return None
c_make_scanner = _import_c_make_scanner()
__all__ = ['make_scanner']
NUMBER_RE = re.compile(
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
(re.VERBOSE | re.MULTILINE | re.DOTALL))
def py_make_scanner(context):
parse_object = context.parse_object
parse_array = context.parse_array
parse_string = context.parse_string
match_number = NUMBER_RE.match
encoding = context.encoding
strict = context.strict
parse_float = context.parse_float
parse_int = context.parse_int
parse_constant = context.parse_constant
object_hook = context.object_hook
object_pairs_hook = context.object_pairs_hook
memo = context.memo
def _scan_once(string, idx):
try:
nextchar = string[idx]
except IndexError:
raise StopIteration
if nextchar == '"':
return parse_string(string, idx + 1, encoding, strict)
elif nextchar == '{':
return parse_object((string, idx + 1), encoding, strict,
_scan_once, object_hook, object_pairs_hook, memo)
elif nextchar == '[':
return parse_array((string, idx + 1), _scan_once)
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
return None, idx + 4
elif nextchar == 't' and string[idx:idx + 4] == 'true':
return True, idx + 4
elif nextchar == 'f' and string[idx:idx + 5] == 'false':
return False, idx + 5
m = match_number(string, idx)
if m is not None:
integer, frac, exp = m.groups()
if frac or exp:
res = parse_float(integer + (frac or '') + (exp or ''))
else:
res = parse_int(integer)
return res, m.end()
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
return parse_constant('NaN'), idx + 3
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
return parse_constant('Infinity'), idx + 8
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
return parse_constant('-Infinity'), idx + 9
else:
raise StopIteration
def scan_once(string, idx):
try:
return _scan_once(string, idx)
finally:
memo.clear()
return scan_once
make_scanner = c_make_scanner or py_make_scanner
+43
View File
@@ -0,0 +1,43 @@
r"""Command-line tool to validate and pretty-print JSON
Usage::
$ echo '{"json":"obj"}' | python -m simplejson.tool
{
"json": "obj"
}
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
Expecting property name: line 1 column 2 (char 2)
"""
import sys
import simplejson as json
def main():
if len(sys.argv) == 1:
infile = sys.stdin
outfile = sys.stdout
elif len(sys.argv) == 2:
infile = open(sys.argv[1], 'rb')
outfile = sys.stdout
elif len(sys.argv) == 3:
infile = open(sys.argv[1], 'rb')
outfile = open(sys.argv[2], 'wb')
else:
raise SystemExit(sys.argv[0] + " [infile [outfile]]")
try:
try:
obj = json.load(infile,
object_pairs_hook=json.OrderedDict,
use_decimal=True)
except ValueError, e:
raise SystemExit(e)
json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
outfile.write('\n')
finally:
infile.close()
outfile.close()
if __name__ == '__main__':
main()
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Pythonic simple JSON RPC Client implementation"
__author__ = "Mariano Reingart (reingart@gmail.com)"
__copyright__ = "Copyright (C) 2011 Mariano Reingart"
__license__ = "LGPL 3.0"
__version__ = "0.04"
import urllib
from xmlrpclib import Transport, SafeTransport
from cStringIO import StringIO
import random
import sys
try:
import gluon.contrib.simplejson as json # try web2py json serializer
except ImportError:
try:
import json # try stdlib (py2.6)
except:
import simplejson as json # try external module
class JSONRPCError(RuntimeError):
"Error object for remote procedure call fail"
def __init__(self, code, message):
self.code = code
self.message = message
def __unicode__(self):
return u"%s: %s" % (self.code, self.message)
def __str__(self):
return self.__unicode__().encode("ascii","ignore")
class JSONDummyParser:
"json wrapper for xmlrpclib parser interfase"
def __init__(self):
self.buf = StringIO()
def feed(self, data):
self.buf.write(data)
def close(self):
return self.buf.getvalue()
class JSONTransportMixin:
"json wrapper for xmlrpclib transport interfase"
def send_content(self, connection, request_body):
connection.putheader("Content-Type", "application/json")
connection.putheader("Content-Length", str(len(request_body)))
connection.endheaders()
if request_body:
connection.send(request_body)
# todo: add gzip compression
def getparser(self):
# get parser and unmarshaller
parser = JSONDummyParser()
return parser, parser
class JSONTransport(JSONTransportMixin, Transport):
pass
class JSONSafeTransport(JSONTransportMixin, SafeTransport):
pass
class ServerProxy(object):
"JSON RPC Simple Client Service Proxy"
def __init__(self, uri, transport=None, encoding=None, verbose=0):
self.location = uri # server location (url)
self.trace = verbose # show debug messages
self.exceptions = True # raise errors? (JSONRPCError)
self.timeout = None
self.json_request = self.json_response = ''
type, uri = urllib.splittype(uri)
if type not in ("http", "https"):
raise IOError, "unsupported JSON-RPC protocol"
self.__host, self.__handler = urllib.splithost(uri)
if transport is None:
if type == "https":
transport = JSONSafeTransport()
else:
transport = JSONTransport()
self.__transport = transport
self.__encoding = encoding
self.__verbose = verbose
def __getattr__(self, attr):
"pseudo method that can be called"
return lambda *args: self.call(attr, *args)
def call(self, method, *args):
"JSON RPC communication (method invocation)"
# build data sent to the service
request_id = random.randint(0, sys.maxint)
data = {'id': request_id, 'method': method, 'params': args, }
request = json.dumps(data)
# make HTTP request (retry if connection is lost)
response = self.__transport.request(
self.__host,
self.__handler,
request,
verbose=self.__verbose
)
# store plain request and response for further debugging
self.json_request = request
self.json_response = response
# parse json data coming from service
# {'version': '1.1', 'id': id, 'result': result, 'error': None}
response = json.loads(response)
if response['id'] != request_id:
raise JSONRPCError(0, "JSON Request ID != Response ID")
self.error = response.get('error', {})
if self.error and self.exceptions:
raise JSONRPCError(self.error.get('code', 0), self.error.get('message', ''))
return response.get('result')
if __name__ == "__main__":
# basic tests:
location = "http://www.web2py.com.ar/webservices/sample/call/jsonrpc"
client = ServerProxy(location, verbose='--verbose' in sys.argv,)
print client.add(1, 2)
+114
View File
@@ -0,0 +1,114 @@
SMSCODES = {
'Aliant':'@chat.wirefree.ca',
'Alltel':'@message.alltel.com',
'Ameritech':'@paging.acswireless.com',
'AT&T':'@txt.att.net',
'AU by KDDI':'@ezweb.ne.jp',
'BeeLine GSM':'@sms.beemail.ru',
'Bell Mobility Canada':'@txt.bellmobility.ca',
'Bellsouth':'@bellsouth.cl',
'BellSouth Mobility':'@blsdcs.net',
'Blue Sky Frog':'@blueskyfrog.com',
'Boost':'@myboostmobile.com',
'Cellular South':'@csouth1.com',
'CellularOne':'@mobile.celloneusa.com',
'CellularOne West':'@mycellone.com',
'Cincinnati Bell':'@gocbw.com',
'Claro':'@clarotorpedo.com.br',
'Comviq':'@sms.comviq.se',
'Dutchtone/Orange-NL':'@sms.orange.nl',
'Edge Wireless':'@sms.edgewireless.com',
'EinsteinPCS / Airadigm Communications':'@einsteinsms.com',
'EPlus':'@smsmail.eplus.de',
'Fido Canada':'@fido.ca',
'Golden Telecom':'@sms.goldentele.com',
'Idea Cellular':'@ideacellular.net',
'Kyivstar':'@sms.kyivstar.net',
'LMT':'@sms.lmt.lv',
'Manitoba Telecom Systems':'@text.mtsmobility.com',
'Meteor':'@sms.mymeteor.ie',
'Metro PCS':'@mymetropcs.com',
'Metrocall Pager':'@page.metrocall.com',
'MobileOne':'@m1.com.sg',
'Mobilfone':'@page.mobilfone.com',
'Mobility Bermuda':'@ml.bm',
'Netcom':'@sms.netcom.no',
'Nextel':'@messaging.nextel.com',
'NPI Wireless':'@npiwireless.com',
'O2':'@o2.co.uk',
'O2 M-mail':'@mmail.co.uk',
'Optus':'@optusmobile.com.au',
'Orange':'@orange.net',
'Oskar':'@mujoskar.cz',
'Pagenet':'@pagenet.net',
'PCS Rogers':'@pcs.rogers.com',
'Personal Communication':'@pcom.ru',
'Plus GSM Poland':'@text.plusgsm.pl',
'Powertel':'@ptel.net',
'Primtel':'@sms.primtel.ru',
'PSC Wireless':'@sms.pscel.com',
'Qualcomm':'@pager.qualcomm.com',
'Qwest':'@qwestmp.com',
'Safaricom':'@safaricomsms.com',
'Satelindo GSM':'@satelindogsm.com',
'SCS-900':'@scs-900.ru',
'Simple Freedom':'@text.simplefreedom.net',
'Skytel - Alphanumeric':'@skytel.com',
'Smart Telecom':'@mysmart.mymobile.ph',
'Southern Linc':'@page.southernlinc.com',
'Sprint PCS':'@messaging.sprintpcs.com',
'Sprint PCS - Short Mail':'@sprintpcs.com',
'SunCom':'@tms.suncom.com',
'SureWest Communications':'@mobile.surewest.com',
'SwissCom Mobile':'@bluewin.ch',
'T-Mobile Germany':'@T-D1-SMS.de',
'T-Mobile Netherlands':'@gin.nl',
'T-Mobile UK':'@t-mobile.uk.net',
'T-Mobile USA (tmail)':'@tmail.com',
'T-Mobile USA (tmomail)':'@tmomail.net',
'Tele2 Latvia':'@sms.tele2.lv',
'Telefonica Movistar':'@movistar.net',
'Telenor':'@mobilpost.no',
'Telia Denmark':'@gsm1800.telia.dk',
'Telus Mobility':'@msg.telus.com',
'The Phone House':'@sms.phonehouse.de',
'TIM':'@timnet.com',
'UMC':'@sms.umc.com.ua',
'Unicel':'@utext.com',
'US Cellular':'@email.uscc.net',
'Verizon Wireless (vtext)':'@vtext.com',
'Verizon Wireless (airtouchpaging)':'@airtouchpaging.com',
'Verizon Wireless (myairmail)':'@myairmail.com',
'Vessotel':'@pager.irkutsk.ru',
'Virgin Mobile Canada':'@vmobile.ca',
'Virgin Mobile USA':'@vmobl.com',
'Vodafone Italy':'@sms.vodafone.it',
'Vodafone Japan (n)':'@n.vodafone.ne.jp',
'Vodafone Japan (d)':'@d.vodafone.ne.jp',
'Vodafone Japan (r)':'@r.vodafone.ne.jp',
'Vodafone Japan (k)':'@k.vodafone.ne.jp',
'Vodafone Japan (t)':'@t.vodafone.ne.jp',
'Vodafone Japan (q)':'@q.vodafone.ne.jp',
'Vodafone Japan (s)':'@s.vodafone.ne.jp',
'Vodafone Japan (h)':'@h.vodafone.ne.jp',
'Vodafone Japan (c)':'@c.vodafone.ne.jp',
'Vodafone Spain':'@vodafone.es',
'Vodafone UK':'@vodafone.net',
'Weblink Wireless':'@airmessage.net',
'WellCom':'@sms.welcome2well.com',
'WyndTell':'@wyndtell.com',
}
def sms_email(number,provider):
"""
>>> print sms_email('1 (312) 375-6536','T-Mobile USA (tmail)')
print 13123756536@tmail.com
"""
import re
if number[0]=='+1': number=number[1:]
elif number[0]=='+': number=number[3:]
elif number[:2]=='00': number=number[3:]
number=re.sub('[^\d]','',number)
return number+SMSCODES[provider]

Some files were not shown because too many files have changed in this diff Show More