New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
More field tests + JSONField #20
Conversation
Pull Request Test Coverage Report for Build 26
💛 - Coveralls |
@Zeliboba5 Please review this. |
tortoise/fields.py
Outdated
|
||
def to_python_value(self, value): | ||
if value is None or isinstance(value, self.type): | ||
return value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should stay, I came up with the bug that we currently having on values
and values_list
- they don't serialise results with to_python_value
. I came up with such test to demonstrate it:
async def test_json_values(self):
data = {'some': ['text', 3]}
obj0 = await testmodels.JSONFields.create(data=data)
values = await testmodels.JSONFields.filter(
id=obj0.id,
).values('data')
self.assertDictEqual(values[0]['data'], data) # fails because values[0]['data'] is string
Could you please return this assertion of is None
and fix values
and values_list
executing to serialise results
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The to_python_value
methods are only called from tortoise/models.py
L333
and there it is after a guard like so:
if not isinstance(value, field_object.type) and value is not None:
setattr(self, key, field_object.to_python_value(value))
So that logic is duplicate.
Adding that code I took away back doesn't resolve it.
What I did wrong there is defining the type of the JSONField, as only (dict, list)
whereas JSON supports more base types. The problem comes in with it being a str
that is how it knows it should serialise/deserialise. I feel to solve this properly, instead of a "to_db_value" and a "to_python_value" we should change it to be a "to_db_value" and "from_db_value" (in principal).
But the current system is better ito lazy evaluation...
Another short-term possibility is to state that JSONField only works for dicts/lists as the root element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I missed your point. It is a good one, and I should add _values and _values_list tests for all fields.
We might have to consider using to_python_value for that, as date/datetime would also cause issues for SQLite...
What I said about it assuming a 'str' is the serialised json object is still valid for that use case.
TO_DB_OVERRIDE = { | ||
fields.BooleanField: to_db_bool, | ||
fields.DecimalField: to_db_decimal, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such way to implement custom behaviour for fields in drivers seems quite hacky and not flexible enough, because it only allows to override already existing methods.
How about we do it this way:
- In module of driver you implement custom fields inheriting from their basic parent (Like
PostgresJSONField(fields.JSONField):
- You create a mapping of
{<Base class>: <Driver custom class>}
somewhere in module - On
Tortoise.init()
you patch models based on their driver class replacing original class with a class from driver app
There is one more tricky moment with this strategy - different drivers can have different name for field type in db. To have better control over it - fields themselfs should have method like get_db_type()
which will return their type definition. This way we can remove FIELD_TYPE_MAP
from SchemaGenerator
and users will be able to define custom fields with custom db type. This way we can also remove hardcode like this from SchemaGenerator
:
if isinstance(field_object, fields.DecimalField):
field_type = field_type.format(field_object.max_digits, field_object.decimal_places)
elif isinstance(field_object, fields.CharField):
field_type = field_type.format(field_object.max_length)
and replace it with graceful:
def get_db_type(self):
return 'DECIMAL({},{})'.format(self.max_digits, self.decimal_places)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was thinking it would be better to have the DB driver replace the Field instances with their own versions on create. Then all the custom logic could be contained.
The issue is there is a LOT of refactoring that would be required for that. So I wasn't sure what to do with it.
If we want to clean up the code structure, there is a lot of annoyances that are going to get in the way:
- The logic is currently spread out a bit already
- There are quite a few circular imports
- There is a fair amount of duplication.
I should probably do some more investigation around this.
I'll add more tests first, then try and fix the broken ones, then start on the refactor. I will need some time before I can be working on this again (possibly only the weekend). |
Fixed an assert on _values_list to not REQUIRE flat=True if only one parameter. Updated Changelog.
@Zeliboba5 Could you pore over commit 88a2b82 as it is easily the most invasive change I have done so far. |
@Zeliboba5 I also think we should leave the field refactoring out for the next PR, as this PR is already doing 3 different things:
|
88a2b82 seems perfectly sane to me. |
I did some more Field tests, and ported JSONField to be more generic.
Items of work:
to_db_value
overrides (for SQLite)values()
andvalues_list()
now converts field values to python typesBugs uncovered/fixed:
0
,1
&null
values_list()
to not REQUIREflat=True
if only one parameter is providedOut of scope, because this PR is getting quite big: