Skip to content
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

MODEL_INSTANCE TypeVar and MODEL_CLASS TypeAlias #1352

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

gh0st-work
Copy link
Contributor

@gh0st-work gh0st-work commented Mar 12, 2023

Description

Added TypeVar for MODEL_INSTANCE and MODEL_CLASS TypeAlias, changed it in all modules.

MODEL_INSTANCE = TypeVar("MODEL_INSTANCE", bound="Model")
MODEL_CLASS: TypeAlias = Type[MODEL_INSTANCE]
MODEL_META_INSTANCE = TypeVar("MODEL_META_INSTANCE", bound="ModelMeta")

Motivation and Context

Helps to simplify and pass typing, improves code readability. Ex: in some funcs and modules there are Model class, MODEL orig TypeVar and model param. So it's quite confusing.

Also, my original motivation was to create relational module that automatically fill return types (ex: ...Relation[Model]) and automatically gets full module path.
Ex:

interlayer

class RelationalFields:

    def __init__(self, app_name: str):  # or somehow in tortoise initialization stage
        if app_name is None:
            raise Exception('app_name is required')
        self.app_name = app_name

    def get_model_name(self, model_class: MODEL_CLASS | str) -> str:
        if model_class is None:
            raise Exception('model_class is required')
        if isinstance(model_class, str):
            return self.app_name + '.' + model_class
        else:
            model_class: MODEL_CLASS = model_class
            if issubclass(model_class, models.Model):
                return self.app_name + '.' + model_class.__name__
            else:
                raise Exception(f'model_class is not string and not Model')

    def OneToOne(
        self,
        model_class: MODEL_CLASS | str,
        related_name: Union[Optional[str], Literal[False]] = None,
        on_delete: str = CASCADE,
        db_constraint: bool = True,
        source_field: Optional[str] = None,
        generated: bool = False,
        pk: bool = False,
        null: bool = False,
        default: Any = None,
        unique: bool = False,
        index: bool = False,
        description: Optional[str] = None,
        model: Optional[MODEL_CLASS] = None,
        validators: Optional[List[Union[Validator, Callable]]] = None,
        **kwargs: Any,
    ) -> OneToOneRelation[MODEL_INSTANCE]:
        model_name = self.get_model_name(model_class)
        return OneToOneFieldInstance(
            model_name=model_name,
            related_name=related_name,
            on_delete=on_delete,
            db_constraint=db_constraint,
            source_field=source_field,
            generated=generated,
            pk=pk,
            null=null,
            default=default,
            unique=unique,
            index=index,
            description=description,
            model=model,
            validators=validators,
            **kwargs
        )

    def ForeignKey(
        self,
        model_class: MODEL_CLASS | str,
        related_name: Union[Optional[str], Literal[False]] = None,
        on_delete: str = CASCADE,
        db_constraint: bool = True,
        source_field: Optional[str] = None,
        generated: bool = False,
        pk: bool = False,
        null: bool = False,
        default: Any = None,
        unique: bool = False,
        index: bool = False,
        description: Optional[str] = None,
        model: Optional[MODEL_CLASS] = None,
        validators: Optional[List[Union[Validator, Callable]]] = None,
        **kwargs: Any,
    ) -> ForeignKeyRelation[MODEL_INSTANCE]:
        model_name = self.get_model_name(model_class)
        return ForeignKeyFieldInstance(
            model_name=model_name,
            related_name=related_name,
            on_delete=on_delete,
            db_constraint=db_constraint,
            source_field=source_field,
            generated=generated,
            pk=pk,
            null=null,
            default=default,
            unique=unique,
            index=index,
            description=description,
            model=model,
            validators=validators,
            **kwargs
        )

    def ManyToMany(
        self,
        model_class: MODEL_CLASS | str,
        through: Optional[str] = None,
        forward_key: Optional[str] = None,
        backward_key: str = "",
        related_name: str = "",
        on_delete: str = CASCADE,
        db_constraint: bool = True,
        source_field: Optional[str] = None,
        generated: bool = False,
        pk: bool = False,
        null: bool = False,
        default: Any = None,
        unique: bool = False,
        index: bool = False,
        description: Optional[str] = None,
        model: Optional[MODEL_CLASS] = None,
        validators: Optional[List[Union[Validator, Callable]]] = None,
        **kwargs: Any,
    ) -> ManyToManyRelation[MODEL_INSTANCE]:
        model_name = self.get_model_name(model_class)
        return ManyToManyFieldInstance(  # type: ignore
            model_name=model_name,
            through=through,
            forward_key=forward_key,
            backward_key=backward_key,
            related_name=related_name,
            on_delete=on_delete,
            db_constraint=db_constraint,
            source_field=source_field,
            generated=generated,
            pk=pk,
            null=null,
            default=default,
            unique=unique,
            index=index,
            description=description,
            model=model,
            validators=validators,
            **kwargs,
        )

Example usage

rel = RelationalFields('app.models')  # or somehow in tortoise initialization stage


class User(Model):
    ...

class Chat(Model):
    owner = rel.ForeignKey(User)  # IDEs type: ForeignKeyFieldRelation[User]
    ...


...
owner = await chat.owner  # IDEs type: User

This relations rewrite not finished yet, so not added & may be added in the future.

How Has This Been Tested?

Ran pytest, only non mine errors:

============================================================================================ short test summary info ============================================================================================= 
FAILED tests/test_queryset.py::TestQueryset::test_delete - tortoise.exceptions.OperationalError: near "ORDER": syntax error
FAILED tests/test_update.py::TestUpdate::test_bulk_update_json_value - tortoise.exceptions.OperationalError: unrecognized token: "{"
====================================================================== 2 failed, 1059 passed, 71 skipped, 4 xfailed, 117 warnings in 38.77s ======================================================================

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added the changelog accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant