Open green3g opened 7 years ago
Hm this might be a bug in Flask-Restless, I'll look into it. Here is a failing test case that I will add to tests/test_updating.py
:
def test_updating_aware_datetime(self):
# GitHub issue #630.
now = datetime.now(tz=timezone.utc)
person = self.Person(id=1, birth_datetime=now)
self.session.add(person)
self.session.commit()
later = datetime.now(tz=timezone.utc)
data = {
'data': {
'type': 'person',
'id': '1',
'attributes': {
'birth_datetime': later.isoformat()
}
}
}
response = self.app.patch('/api/person/1', data=dumps(data))
print(response.data)
self.assertEqual(response.status_code, 204)
self.assertEqual(person.birth_datetime, later)
Actually, I believe this is a problem with the backend. Depending on your database, you may not have timezone support (or even datetime type support). I'm not sure what your database backend is, but you may try setting the timezone=True
flag in the DateTime
constructor; for more information, see http://docs.sqlalchemy.org/en/latest/core/type_basics.html#sqlalchemy.types.DateTime.
For example, in my unit tests, since I am using SQLite, even if I set timezone=True
in the column, the following code demonstrates the problem:
now = datetime.now(tz=timezone.utc)
person = self.Person(id=1, birth_datetime=now)
self.session.add(person)
self.session.commit()
print(now)
# 2017-02-06 05:00:49.657781+00:00
print(person.birth_datetime)
# 2017-02-06 05:00:49.657781
So are you certain you have support for timezones in your database backend?
I'm using SQL Server, the datetime
type, but I've also tried the datetime2
type. Neither one looks like it has time zone support, so shouldn't the default (timezone=False
) work correctly?
What I'm saying is that your client is making a request to set a datetime field with a timezone-aware object; the "Z" in the datetime object in your original example indicates timezone-aware:
>>> from dateutil.parser import parse
>>> parse('2017-01-24T06:00:00.000Z')
datetime.datetime(2017, 1, 24, 6, 0, tzinfo=tzutc())
>>> parse('2017-01-24T06:00:00.000')
datetime.datetime(2017, 1, 24, 6, 0)
However, your database is timezone-naive, like in the second print statement in my example code above:
print(now)
# 2017-02-06 05:00:49.657781+00:00
print(person.birth_datetime)
# 2017-02-06 05:00:49.657781
So the request is a timezone-aware object, but the database is timezone-naive. There is a mismatch, and therefore SQLAlchemy raises an exception. None of this has anything to do with Flask-Restless except for the fact that we correctly parse your timezone-aware datetime from the string in the body of the request.
I will add a test that ensures a meaningful error message is sent to the client when this happens, but other than that, I'm going to mark this is as "not a bug".
Actually, the test I wrote in https://github.com/jfinkels/flask-restless/issues/630#issuecomment-277586698 doesn't demonstrate cause the TypeError
that you initially reported. Perhaps it is particular to Microsoft SQL Server? @roemhildtg if you are able, can you provide a minimal working example that includes the Microsoft SQL Server-specific configuration code (since I don't use Microsoft Windows)?
I'll work on a minimal example.
Here's an update, I think this might have something to do with the issue. It also explains why I am so confused. One server PATCH requests are working correctly, the other is not, despite using the same code.
My overall workflow is the object is retrieved via GET, edited in a form, and then submitted back to flask restless via PATCH.
On the GET request, each server returns the object, but there is a discrepancy between the two in one of the date field, even though the data is the same.
2017-02-28 08:10:30.4970000
2017-02-28T08:10:30.497000
On the PATCH request:
"date_modified":"2017-02-28 08:10:30.4970000"
"date_modified":"2017-02-28T08:10:30.497000",
Could that have something to do with the issue?
Okay, here's a minimal example:
Created a table with id and date field:
import flask
import flask_sqlalchemy
import flask_restless
app = flask.Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'mssql+pyodbc://gis'
db = flask_sqlalchemy.SQLAlchemy(app)
class DateTest(db.Model):
__table_args__ = {'schema':'dbo'}
__tablename__ = 'test_date_bug'
id = db.Column(db.Integer, primary_key=True)
some_date = db.Column(db.DateTime,)
manager = flask_restless.APIManager(app, flask_sqlalchemy_db=db)
manager.create_api(DateTest, methods=['GET', 'POST', 'DELETE', 'PATCH'])
app.run(host='0.0.0.0')
POST /api/test_date_bug
{"data":{"attributes":{"some_date":"2017-02-28T08:10:30.497Z"},"type":"test_date_bug"}}
RESULT
{
"data": {
"attributes": {
"some_date": "2017-02-28T08:10:30.497000+00:00"
},
"id": "9",
"links": {
"self": "/api/test_date_bug/9"
},
"type": "test_date_bug"
},
"included": [],
"jsonapi": {
"version": "1.0"
},
"links": {},
"meta": {}
}
GET /api/test_date_bug/9
{
"data": {
"attributes": {
"some_date": "2017-02-28T08:10:30.497000"
},
"id": "9",
"links": {
"self": "/api/test_date_bug/9"
},
"type": "test_date_bug"
},
"included": [],
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/api/test_date_bug/9"
},
"meta": {}
}
PATCH /api/test_date_bug/9
{"data":{"attributes":{"some_date":"2017-02-28T08:10:30.497000+00:00"},"type":"test_date_bug","id":"9"}}
Results in error. I think since the data is not timezone aware, flask restless should not return timezone aware dates in the POST and GET requests
Thanks for the example, I'll take a look at this.
Let's deal with each request individually.
What can we do about this (item 4)? I'm not sure. I don't want Flask-Restless to catch every possible type of error that might be raised (it's already catching essentially all SQLAlchemy exceptions). I'm not sure there's anything I can do about this. As a workaround, you can use a preprocessor to strip the timezones from the incoming requests.
As for your earlier comment about different versions of Python producing different versions of datetime strings, I'm guessing you are using print(d)
in one place and print(d.isoformat())
in another:
$ python2 -c "from datetime import datetime; now = datetime.now(); print(now); print(now.isoformat())"
2017-03-25 00:08:04.802940
2017-03-25T00:08:04.802940
So again that is not a Flask-Restless issue.
I have created a pull request #647 to fix item 2 in my list above.
@jfinkels sounds good. Thanks for taking a look
Running into this issue if I have a model like this:
And I send a request to flask-restless like this:
PATCH /api/plan_index/3936
It only happens once there is a value set on that column. If the value is null, and I set a new datetime, the PATCH is successful.
Also, posting new features with values is successful. It is only updating existing datetime values.
I'm not sure if this is a problem with flask-restless or not, but it seems that flask-restless is the first logical step to solving it.
My data is in sql server, and some of the values look like this:
The column type in sql server is
datetime
.