Python Colander library is failing with exception 'createdon': u'Invalid date' when createdon field contains the value datetime.datetime.now()
Pergunta
I'm using python's colander library for validation. In my code there is a createdon field of colander.DateTime() type. When I'm providing it a value of datetime.datetime.now() it is failing with exception saying that createdon field has invalid date. What may be the problem?
Here is the code of python module :
import colander
import htmllaundry
import pymongo
import random
import datetime
import hashlib
from pymongo import Connection
# Database Connection
HOST = "localhost"
PORT = 27017
DATABASE = "testdb"
conn = Connection(HOST, PORT)
db = conn[DATABASE]
# function to generate random string
def getRandomString(wordLen):
word = ''
for i in range(wordLen):
word += random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
return word
# Colander classe for User object
class User(colander.MappingSchema):
username = colander.SchemaNode(colander.String(),validator=colander.Length(3,100))
email = colander.SchemaNode(colander.String(),validator=colander.All(colander.Email(),colander.Length(3,254)))
password = colander.SchemaNode(colander.String(),validator=colander.Length(8,100))
isactive = colander.SchemaNode(colander.Boolean())
code = colander.SchemaNode(colander.String(),validator=colander.Length(64,64))
name = colander.SchemaNode(colander.String(),validator=colander.Length(3,100))
picture = colander.SchemaNode(colander.String())
about = colander.SchemaNode(colander.String(),preparer=htmllaundry.sanitize,validator=colander.Length(0,1024))
ipaddress = colander.SchemaNode(colander.String())
createdon = colander.SchemaNode(colander.DateTime())
updatedon = colander.SchemaNode(colander.DateTime())
status = colander.SchemaNode(colander.Int(),validator=colander.Range(0,4)) #0->active, 1->Deleted, 2->Suspended, 3->Deactivated
# getUser(username)
def getUser(username):
user = db.users.find_one({"username" : username })
return user
# getUserByEmail(email)
def getUserByEmail(email):
user = db.users.find_one({"email" : email })
return user
# createUser(userdata) #accepts a dictionary argument
def createUser(userdata):
schema = User() # generate schema object for User Validation Class
try:
# set current date/time in createdon/updatedon
userdata['createdon'] = datetime.datetime.now()
userdata['updatedon'] = userdata['createdon']
# generate unique activation code, set isactive to False, and set status to 0
randomCode = getRandomString(64)
userdata['code'] = hashlib.sha256(randomCode).hexdigest()
userdata['isactive'] = False
userdata['status'] = 0
# validate and deserialize userdata
schema.deserialize(userdata)
# sha256 the password
userdata['password'] = hashlib.sha256(userdata['password']).hexdigest()
# save the userdata object in mongodb database
result = db.users.insert(userdata)
# return the result of database operation and final userdata object
return result, userdata
except colander.Invalid, e:
errors = e.asdict()
return errors, userdata
Here is how I'm using it in test.py :
import usermodule
UserObject = {
'username':'anuj',
'email': 'anuj.kumar@gmail.com',
'password':'testpassword',
'name':'Anuj Kumar',
'picture':'/data/img/1.jpg',
'about':'Hacker & Designer, New Delhi',
'ipaddress':'127.0.0.1'
}
result, data = usermodule.createUser(UserObject)
print result
print data
and I'm getting following error :
anuj@anuj-Vostro-1450:~/Projects/test$ python test.py
{'createdon': u'Invalid date', 'updatedon': u'Invalid date'}
{'username': 'anuj', 'picture': '/data/img/1.jpg', 'about': 'Hacker & Designer, New Delhi', 'code': 'd6450b49e760f96256886cb24c2d54e8e8033293c479ef3976e6cbeabbd9d1f1', 'name': 'Anuj Kumar', 'updatedon': datetime.datetime(2012, 9, 14, 16, 16, 32, 311705), 'createdon': datetime.datetime(2012, 9, 14, 16, 16, 32, 311705), 'status': 0, 'password': 'testpassword', 'ipaddress': '127.0.0.1', 'email': 'anuj.kumar@gmail.com', 'isactive': False}
Solução
You are deserializing a cstruct, and it contains a datetime.datetime
instance. Colander expects only simple types such as int
, float
, str
, etc.
If you make your datetime value a ISO-formatted string, things work just fine:
>>> import datetime
>>> import colander
>>> colander.SchemaNode(colander.DateTime()).deserialize(datetime.datetime.now())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/private/tmp/colander/lib/python2.7/site-packages/colander/__init__.py", line 1598, in deserialize
appstruct = self.typ.deserialize(self, cstruct)
File "/private/tmp/colander/lib/python2.7/site-packages/colander/__init__.py", line 1265, in deserialize
mapping={'val':cstruct, 'err':e}))
colander.Invalid: {'': u'Invalid date'}
>>> colander.SchemaNode(colander.DateTime()).deserialize(datetime.datetime.now().isoformat())
datetime.datetime(2012, 9, 14, 13, 5, 37, 666630, tzinfo=<colander.iso8601.Utc object at 0x109732d50>)
Note that datetime.datetime.now()
does not include timezone information, nor would .isoformat()
preserve that. Colander, on the other hand, parses the ISO 8601 string as a timestamp in the UTC timezone, so you want to generate your timestamp in the same timezone by using the datetime.datetime.utcnow()
class method instead:
>>> colander.SchemaNode(colander.DateTime()).deserialize(datetime.datetime.utcnow().isoformat())
datetime.datetime(2012, 9, 14, 11, 23, 25, 695256, tzinfo=<colander.iso8601.Utc object at 0x1005aaf10>)
So, replace
userdata['createdon'] = datetime.datetime.now()
with
userdata['createdon'] = datetime.datetime.utcnow().isoformat()
and colander will happily parse that for you, using the correct timezone.