Closed Abdeldjalil-H closed 6 days ago
This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Files with Coverage Reduction | New Missed Lines | % | ||
---|---|---|---|---|
tortoise/contrib/postgres/regex.py | 2 | 75.0% | ||
tortoise/init.py | 2 | 95.76% | ||
tortoise/contrib/pydantic/creator.py | 2 | 98.91% | ||
tortoise/router.py | 4 | 68.42% | ||
tortoise/expressions.py | 7 | 97.25% | ||
tortoise/validators.py | 7 | 81.05% | ||
tortoise/backends/base/executor.py | 8 | 91.37% | ||
tortoise/contrib/postgres/json_functions.py | 10 | 35.71% | ||
tortoise/queryset.py | 16 | 94.37% | ||
tortoise/contrib/mysql/json_functions.py | 17 | 38.71% | ||
<!-- | Total: | 111 | --> |
Totals | |
---|---|
Change from base Build 11833519941: | 0.4% |
Covered Lines: | 6235 |
Relevant Lines: | 6855 |
I am okay with merging it that way - but is it really what users in original issue needed?
Probably would be a little bit easier for them, but looks like they wanted to have ability to override table name generation globally
Although not sure how to do it in clean way - may be make some base method to determine table, and it would by default take table name from meta, so users could override such method? Not a super fan of that idea, as it would go against current logic that such things are determined by model meta, but not sure of better ideas right now
I agree that it is better to have a gloabal way to change the table names. One approach I can think about is to pass table_name_generator
to Tortoise.init
. What do you think about this?
I think that could work
Although then we would be back to problem of conflict between Meta.table_name
and this init function, which could be confusing
@henadzit @waketzheng may be you have any good ideas how interface for that could look like?
I think in that case it is not much confusing. The Meta.table
will take precidence. It is like overriding an existing value.
I think in that case it is not much confusing. The Meta.table will take precidence. It is like overriding an existing value.
Yeah, I think you are right
Let's try to do it that way
Also, we can add an example to show generating snake_case_table_names global.
@Abdeldjalil-H are you planning to work on that in near future?
I was thinking if I should wait for it to be ready to include in 0.22.0 release
Hi! Sorry, I'm a bit late here.
A few thoughts/questions from me:
table_name_generator
is global. What if you have two different groups of models and you want to have different naming pattern for them? Looks like it's possible to achieve that with a Meta class in Django https://stackoverflow.com/questions/51353106/is-there-a-django-setting-to-generate-multiple-words-table-names-for-multiplewor. Maybe, it's something we can explore?Hi @henadzit. Thank you for your questions.
db_table
value which is set either from Meta.table
or uses the default lower case if not provided. This PR just added the option to use any function for default instead of lowercased model name.sqlalchemy
has the option __tablename__
as a function.from tortoise import Model
class ModelBaseA(Model):
class Meta:
abstract = True
class ModelBaseB(Model):
class Meta:
abstract = True
def generate_table_name(model_cls):
if isinstance(model_cls, ModelBaseA):
# first pattern ...
if isinstance(model_cls, ModelBaseB):
# second pattern ...
await Tortoise.init(..., table_name_generator=generate_table_name)
another idea is to add table_name_generator
to configs.apps
, so each app will have its name generator. We may also use both the global one and app level one, so we can set a general default and a custom one.
Please let me know what do you think?
Hey @Abdeldjalil-H, again, sorry for a late feedback!
class PrefixMeta:
@staticmethod
def table(model):
return "prefix_" + model.__name__.lower()
class User(Model):
class Meta(PrefixMeta):
pass
id = fields.IntField(pk=True)
This would require adding a callable
check in tortoise/__init__.py
:
if callable(model._meta.db_table):
model._meta.db_table = model._meta.db_table(model)
elif not model._meta.db_table:
model._meta.db_table = model.__name__.lower()
I'm not a fan of generate_table_name
checking isintance
and deciding on naming since you have to make it aware of all the models. This might be an issue for large organizations.
@henadzit I'll check for aerich.
The solution you proposed definitely works, but you need to inherit from PrefixMeta
on each all models. Of course this is better than defining custom table name for all tables, but I think we can do better. Your solution inspired me about the following solution
from tortoise.models import Model, ModelMeta
class PrefixTableNameMeta(ModelMeta):
def __new__(mcs, name: str, *args):
new_class = super().__new__(mcs, name, *args)
new_class._meta.db_table = "prefix_" + name.lower()
return new_class
class User(Model, metaclass=PrefixTableNameMeta):
id = fields.IntField(pk=True)
While this solution will work without the need of any changes in the original code base, it is not user-friendly. I think the simplest solution we can get is to define something that has the following criterias:
One example that satisfies this is an optional function on the model itself (not meta). We can call it get_table_name
or anything.
Add Table Name Generator Feature
Description
This PR adds a new
table_name_generator
attribute to the Model Meta class. This allows users to dynamically generate table names using a custom function instead of explicitly setting the table name or relying on defaults.The feature includes:
table_name_generator
Meta attribute that accepts a callabletable
name overrides generated namesMotivation and Context
This change provides users with more flexibility in how table names are generated. This should solve #1026 #1181
The feature promotes cleaner code by centralizing table naming logic and reducing repetition across models.
How Has This Been Tested?
The implementation includes a comprehensive test suite that verifies:
Tests are implemented using the existing tortoise.contrib.test.TestCase class and follow the project's testing patterns. All tests pass in the current test environment.
Test scenarios cover:
Checklist: