Closed Torniojaws closed 7 years ago
Bleeehhh, don't code at 3:30 AM :D
The column is nullable=False
AKA NOT NULL
...and move and remove would make it NULL :)
Hmm, actually it also happens with a column that allows NULL values (Updated
):
self = <test_views.TestNewsView testMethod=test_patching_things_using_move>
def test_patching_things_using_move(self):
response = self.app.patch(
"/api/1.0/news/{}".format(int(self.news_ids[0])),
data=json.dumps(
[{
"op": "move",
"from": "/updated",
"path": "/author"
}]
),
content_type="application/json"
)
news = News.query.get_or_404(self.news_ids[0])
self.assertEquals(200, response.status_code)
self.assertNotEquals("UnitTest Author", news.Author)
> self.assertEquals(None, news.Updated)
E AssertionError: None != datetime.datetime(2017, 9, 9, 0, 56, 18)
I have added /update
to the mapping.
python-json-patch
does not - in any way - deal with databases. Can you create a self-contained example that reproduces the error (and does not depend on a database)?
I also verified that the None
does work when I use SQLAlchemy directly:
news.Updated = None
db.session.add(news)
db.session.commit()
news = News.query.get_or_404(self.news_ids[0])
self.assertEquals(None, news.Updated)
# This passes
Edit: Saw your comment. I will create an example now.
Still working on the test case, but I think this is what happens:
move
, the value is correctly removed from the patch data.from
item. Ie. it keeps whatever was there originally. I think it would need to be explicitly defined for it to work, at least in my case.
So if the source data is eg.
{"Author": "Some name", "Updated": "SomeDatetime")
Then the patch result of
{"op": "move", "from": "/Updated", "path": "/Author"}
Would need to be this:
{"Author": "SomeDatetime", "Updated": null}
Instead of this:
{"Author": "SomeDatetime"}
So then it would make the Updated = NULL
in the DB when used with SQLAlchemy.
Will test a bit more and see if that's the case.
Yes, that is the reason. At least in SQLAlchemy + PyMySQL + MariaDB, setting the value explicitly to None
will achieve the desired behaviour.
Example case, which of course will need development to check all valid elements for the same case:
data = patch.apply(data)
print(data)
if "Updated" not in data:
data["Updated"] = None
When used with {"op": "move", "from": "/Updated", "path": "/Author"}
Then the outcome is:
Author
now has the value that was in Updated
Updated
is now NULLSo where exactly do you see a problem with python-json-patch
?
Based on the above, I think there is no real issue in python-json-patch
.
However, I think this is a fairly common use case for it. It would be great to have some extra option that allows you to toggle between removing the dict element completely (current behaviour) or setting it as None
. Maybe something like this:
data = instance.asdict(exclude_pk=True, **kwargs)
patch = JsonPatch(patchdata)
data = patch.apply(data, use_nulls=True) # This would set the "removed" item explicitly as None
instance.fromdict(data)
So, let's say the source data is: {"Author": "Some name", "Updated": "SomeDatetime")
move
:{"op": "move", "from": "/Updated", "path": "/Author"}
data = patch.apply(data, use_nulls=True)
Would result in: {"Author": "SomeDatetime", "Updated": None}
{"op": "move", "from": "/Updated", "path": "/Author"}
data = patch.apply(data, use_nulls=False)
Would result in: {"Author": "SomeDatetime"}
{"op": "move", "from": "/Updated", "path": "/Author"}
data = patch.apply(data)
Would result in: {"Author": "SomeDatetime"}
remove
:{"op": "remove", "path": "/Author"}
data = patch.apply(data, use_nulls=True)
Would result in: {"Author": None, "Updated": "SomeDatetime"}
{"op": "remove", "path": "/Author"}
data = patch.apply(data, use_nulls=False)
Would result in: {"Updated": "SomeDatetime"}
{"op": "remove", "path": "/Author"}
data = patch.apply(data)
Would result in: {"Updated": "SomeDatetime"}
RFC 6902 is pretty clear on that
4.4. move
The "move" operation removes the value at a specified location and
adds it to the target location.
To solve your particular case, I'd recommend something like
import collections
data = instance.asdict(exclude_pk=True, **kwargs)
patch = JsonPatch(patchdata)
data = patch.apply(data)
# wrap in a defaultdict
d = collections.defaultdict(lambda: None)
d.update(data)
instance.fromdict(d)
All the other ops work just fine, but
move
andremove
don't seem to delete anything? Is there some limitation with for example MariaDB and/or SQLAlchemy? I am using the PyMySQL driver.Update: See my third post in this issue with a more valid case in point. In that, the move/remove fails even when the DB column allows NULL values.
The implementation:
PATCH /news/123
The method:
The mapping:
As mentioned, all other cases work just fine and the DB values are patched correctly. But with
move
andremove
it seems nothing happens. I don't have any foreign keys or anything in the DB for the elements which fail.Here's the fail for
move
. What is interesting is that the value is moved correctly to/author
, as it passes the assert. In the beginning of the test, the value of/author
isUnitTest Author
. It's just the deletion of/contents
that fails.The DB: