jonra1993 / fastapi-alembic-sqlmodel-async

This is a project template which uses FastAPI, Pydantic 2.0, Alembic and async SQLModel as ORM. It shows a complete async CRUD using authentication and role base access control.
MIT License
878 stars 142 forks source link

Optional[str] vs. str | None #56

Closed vi-klaas closed 1 year ago

vi-klaas commented 1 year ago

For example here

Would Optional[str] have the same result?

jonra1993 commented 1 year ago

Hello @klav-zhaw yes sure according to new python 3.10 typing link

Optional[X] is equivalent to X | None (or Union[X, None]), so in your question yes the last result is the same I have learned that on pydantic when you set a default value it is also optional

Like this

first_name: str                              #Required field because there is no a default value and None is not allowed
last_name: str                              #Required field because there is no a default value and None is not allowed
company_name: str = "jrtec"     # Optional field and default is 'jrtec'
age: int | None                             # Optional field and default is None
year: int = 2023                          # Optional field and default is 2023

It could be cases where you prefer a default value and others in which you can have None values on the database so str | None is a good option. If you want to learn more about of empty strings or null string on a database and its meaning you can check this link

vi-klaas commented 1 year ago

Follow up question @jonra1993 hashed_password: Optional[str] = Field(nullable=False, index=True)

How do Optional and nullable=false work together?

jonra1993 commented 1 year ago

Hello, @klav-zhaw I think this is wrong I am going to solve it. There could be two scenarios

  1. You want to allow that user not to set a password so hashed_password: str | None = Field(nullable=True, index=True)
  2. It is required that users have always a password so hashed_password: str = Field(nullable=False, index=True) is a better option
vi-klaas commented 1 year ago

Follow-Up on that, @jonra1993 : I tried with option 2.. We then need to add the hashed password to the IUserCreate object somewhere in the chain. You have this config inner class with hashed_password = None Can we add there the hashed password instead of None? Or do we need to override the Create method in the user crud? How would that look like?

I found already that dirty-ish solution

class UserCreate(UserBase):
    password: str
    hashed_password: Optional[str] = field(exclude=True)

    @root_validator
    def hash_password(cls, values) -> Dict:
        values["hashed_password"] = get_password_hash(values["password"])

        return value

or as function

    @validator("hashed_password", always=True)
    def hash_password(cls, v, values, **kwargs) -> str:
        return get_password_hash(values["password"])

Is that the goto solution?

jonra1993 commented 1 year ago

Hello @vi-klaas. In the Pydantic schema, the Config class is used to configure various settings for the schema. In this case, the Config class is being used to define a default value for the hashed_password field, which is set to None. so When a new instance of the IUserCreate class is created, if a value for hashed_password is not provided, it will default to None.

I like this function seems more intuitive

@validator("hashed_password", always=True)
    def hash_password(cls, v, values, **kwargs) -> str:
        return get_password_hash(values["password"])

How was the final result?