Skip to content

PolyModel

This is the object used for all the classes that want to enforce the static typing all over the object itself.

This object is different from the decorator as you don't need to specify which functions should be enforced.

How to use it

When using the PolyModel you must import it first.

from polyforce import PolyModel

Once it is imported you can simply subclass it in your objects. Something like this:

from typing import List, Union

from typing_extensions import Self

from polyforce import PolyModel


class Movie(PolyModel):
    def __init__(
        self,
        name: str,
        year: int,
        tags: Union[List[str], None] = None,
    ) -> None:
        self.name = name
        self.year = year
        self.tags = tags

    def get_movie(self, name: str) -> Self:
        """
        Returns a movie
        """
        ...

    def _set_name(self, name: str) -> None:
        """
        Sets the name of the movie.
        """

    @classmethod
    def create_movie(cls, name: str, year: int) -> Self:
        """
        Creates a movie object
        """
        return cls(name=name, year=year)

    @staticmethod
    def evaluate_movie(name: str, tags: List[str]) -> bool:
        """
        Evaluates a movie in good (true) or bad (false)
        """
        ...

When adding the PolyModel object, will enable the static type checking to happen all over the functions declared in the object.

Ignore the checks

Well, there is not too much benefit of using PolyModel if you want to ignore the checks, correct? Well, yes but you still can do it if you want.

There might be some scenarios where you want to override some checks and ignore the checks.

For this, Polyforce uses the Config dictionary.

You simply need to pass ignore=True and the static type checking will be disabled for the class.

It will look like this:

from typing import List, Union

from typing_extensions import Self

from polyforce import Config, PolyModel


class Movie(PolyModel):
    config: Config(ignore=True)

    def __init__(
        self,
        name: str,
        year: int,
        tags: Union[List[str], None] = None,
    ) -> None:
        self.name = name
        self.year = year
        self.tags = tags

    def get_movie(self, name: str) -> Self:
        """
        Returns a movie
        """
        ...

    def _set_name(self, name: str) -> None:
        """
        Sets the name of the movie.
        """

    @classmethod
    def create_movie(cls, name: str, year: int) -> Self:
        """
        Creates a movie object
        """
        return cls(name=name, year=year)

    @staticmethod
    def evaluate_movie(name: str, tags: List[str]) -> bool:
        """
        Evaluates a movie in good (true) or bad (false)
        """
        ...

Ignore specific types

What if you want to simply ignore some types? Meaning, you might want to pass arbitrary values that you don't want them to be static checked.

from typing import List, Union

from polyforce import Config, PolyModel


class Actor:
    ...


class Movie(PolyModel):
    config: Config(ignored_types=(Actor,))

    def __init__(
        self,
        name: str,
        year: int,
        tags: Union[List[str], None] = None,
    ) -> None:
        self.name = name
        self.year = year
        self.tags = tags
        self.actors: List[Actor] = []

    def add_actor(self, actor: Actor) -> None:
        """
        Returns a movie
        """
        self.actors.append(actor)

This will make sure that the type Actor is actually ignore and assumed as type Any which also means you can pass whatever value you desire since the type Actor is no longer checked.

Integrations

Polyforce works also really well with integrations, for instance with Pydantic.

The only thing you need to do is to import the decorator and use it inside the functions you want to enforce.

from typing import List, Union

from pydantic import BaseModel, ConfigDict

from polyforce import polycheck


class Actor:
    ...


class Movie(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)
    name: str
    year: int
    actors: Union[List[Actor], None] = None

    @polycheck()
    def add_actor(self, actor: Actor) -> None:
        self.actors.append(actor)

    @polycheck()
    def set_actor(self, actor: Actor) -> None:
        ...

This way you can use your favourite libraries with Polyforce.

Inheritance

The PolyModel works with inheritance as per normal subclassing, which means you can still override functions with the same name and perform any inheritance action.

from typing import List, Union

from typing_extensions import Self

from polyforce import Field, PolyModel


class Movie(PolyModel):
    def __init__(
        self,
        name: str,
        year: int,
        tags: Union[List[str], None] = None,
    ) -> None:
        self.name = name
        self.year = year
        self.tags = tags

    def get_movie(self, name: str) -> Self:
        """
        Returns a movie
        """
        ...

    def set_name(self, name: Union[str, int]) -> None:
        """
        Sets the name of the movie.
        """

    @classmethod
    def create_movie(cls, name: str, year: int) -> Self:
        """
        Creates a movie object
        """
        return cls(name=name, year=year)

    @staticmethod
    def evaluate_movie(name: str, tags: List[str]) -> bool:
        """
        Evaluates a movie in good (true) or bad (false)
        """
        ...


class Serie(Movie):
    def __init__(
        self,
        name: str = Field(default="serie"),
        season: int = Field(default=1),
        year: int = Field(default=2023),
    ) -> None:
        super().__init__(name=name, year=year)
        self.season = season

    def set_name(self, name: str) -> None:
        """
        Sets the name of the series.
        """

As you can see, the __init__ was overridden and a new signature was generated ad the set_name for the Serie has now a different signature that will be enforced accordingly.