Hey,
I think new type makes sense to also support generic use cases.
Imagine this use case:
from typing import NewType, TypeVar
SortedList = NewType("SortedList", list)
T = TypeVar("T")
def my_sorted(items: list[T]) -> SortedList[T]: ...
Right now this wouldn’t work.
Following discussion on here: Generic NewType? · Issue #3331 · python/mypy · GitHub
4 Likes
Phantom types might be a more suitable construct for cases like this, as they also more strictly tie together the type and its guarding predicate function. See:
Hey Anton,
I think this discussion is kinda not very related to the “idea” I suggested, you’re fundamentally questioning the new type with your article while it’s something very used and it has a bad limitation right now that it cannot be a generic.
I read the article I suppose you’ve written it, but i think you’ve misunderstood the concept of new type (aka branded type). The point of branded type is to not differentiate the branded type against the actual type where brand type is derived from. You’re eventually branding this type, let’s say I have EmailString.
from typing import NewType
from sqlalchemy import Mapped
from your_project import Base
EmailString = NewType("EmailString", str)
class User(Base): # imagine this is my sqlalchemy model
username: Mapped[EmailString]
def validate_email(email: str) -> EmailString:
... # the actual validate email call
class UserIputSchema(BaseModel):
email: EmailString
@model_validator(mode="after")
def validate_model(self):
validate_email(self.email)
return self
- You mentioned it cannot be used with pydantic → that’s wrong. Even I could annotate the EmailString validator to a AfterValidator, to ensure that validation always run if the type is used in pydantic
- I don’t need to actually validate my email field if it’s coming from SQLA, because I assume validation always run when i’m trying to save in database!
- Following the below quote, code review does! The point of branding type is that you are in control, when you want to brand a type or not. Well you can also cast a type to something competely wrong. That’s how type system works, it allows you to do whatever you want, if you want to explicitly make such mistake, then there must be a good reason or it would be caught immediately in the code reivew process.
Blockquote
In reality though, there is nothing stopping us from importing the TZAware
type in another module and instantiating it with an invalid value
By subclassing a type, you’re losing all functionality that comes with that type. For instance, for the example I provided earlier, if EmailString was actually a class that would be initiated every-time, then I need to create a new “String schema” for EmailString, and make SQLAlchemy to initiate that instead of string in runtime. Then for pydantic to understand that type, I’d have to write a pydantic schema validator. All that comes with runtime cost of checking it, even if i’m sure there’s no need for any check if I checked it once before writing to database. Imagine you’re using some sort of regex to validate if email is actually email, if you validate everytime, then if you fetch a million record of User, then that regex would be called for a million time unnecessarily