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.