BeanieODM / beanie

Asynchronous Python ODM for MongoDB
http://beanie-odm.dev/
Apache License 2.0
2.04k stars 215 forks source link

question: how to avoid insert null value? #237

Closed hd10180 closed 1 year ago

hd10180 commented 2 years ago

hey, roman

how can i do in this case, username is required and unique
email, phone are not required, but if exist, they must be unique
so i wan't to define a unique and sparse index
but everytime i insert document the value None will insert to mongodb
my question is how to avoid insert none ?

hope your reply, thanks!

class User(BaseEntity):

    username: str
    phone: Optional[str]  # nullable, unique and sparse index
    email: Optional[EmailStr]    # nullable, unique and sparse index
    password: bytes

    class Collection:
        name = "user"
        indexes = [ 
            IndexModel([("username", DESCENDING)], unique=True),
            IndexModel([("email", DESCENDING)], unique=True, sparse=True),     # nullable, unique and sparse index
            IndexModel([("phone", DESCENDING)], unique=True, sparse=True),    # nullable, unique and sparse index
        ]
roman-right commented 2 years ago

Hey! You can try to add your validation or null-replacing logic using @before_event(Insert) method decorator. Here is the doc: https://roman-right.github.io/beanie/tutorial/event-based-actions/

hd10180 commented 2 years ago

Hey! You can try to add your validation or null-replacing logic using @before_event(Insert) method decorator. Here is the doc: https://roman-right.github.io/beanie/tutorial/event-based-actions/

ttthanks! i use del user.phone when phone is none in before_event, it work!

rgajason commented 2 years ago

FYI, partial indexes seem to be preferred over sparse indexes in recent Mongo versions:

https://www.mongodb.com/docs/manual/core/index-sparse/ https://www.mongodb.com/docs/manual/core/index-partial/#std-label-index-type-partial

Using partial indexes, your model would look something like this:

class User(Document):
    username: Indexed(str, unique=True)
    phone: Optional[str]
    email: Optional[EmailStr]
    password: bytes

    class Settings:
        name = "user"
        indexes = [ 
            "email",  # optional, includes missing/null, used for searching
            "phone",
            IndexModel(  # ignores missing/null, used for data integrity
                [("email", DESCENDING)],
                name="email_unique_if_not_empty",
                unique=True,
                partialFilterExpression=("email": {"$type": "string"}),
            ),
            IndexModel(
                [("phone", DESCENDING)],
                name="phone_unique_if_not_empty",
                unique=True,
                partialFilterExpression=("phone": {"$type": "string"}),
            ),
        ]

This creates two indexes on the email and phone fields (one unique, the other not) because otherwise all of your searches for email or phone would require you to explicitly filter on {"$type": "string"}, else an index would not be used. Creating the second, non-unique index reduces the amount of code (though at the cost of some Mongo resources).

hd10180 commented 1 year ago

hello everyone, i just find a problem if some use del obj.attr_name to avoid insert null value like me. my enviroment is

beanie==1.13.0
pydantic==1.10.2

my document definition(only as an example):

class User(Document):
    name: str
    password: str
    email: Optional[str]

    @before_event([Insert, Replace, SaveChanges, ValidateOnSave, Update])  # actually i use all these event type looks like  "save" 
    def checker(self):
        if not self.email:
            del self.email

then i found the checker call multiple times, the first time it works well, but the second time(actually i was delete the attribute email), the value of self.email is "email", it looks like it return the attribute name as a value? i have try normal object and pydantic BaseModel,

from typing import Optional, Union

from pydantic import BaseModel

class T:
    def __init__(self) -> None:
        self.a: Optional[Union[str, int]] = None
        self.b: Optional[Union[str, int]] = None

class User(BaseModel):
    id: int
    name: str = "Jane Doe"

if __name__ == "__main__":
    # o = T()
    # o.a = 1
    # print(f"1: {o.a}")
    # if not o.a:
    #     print(f"2: {o.a}")
    #     del o.a
    # print(f"3: {o.a}")  # cause: AttributeError: 'T' object has no attribute 'a'

    u = User(id=1, name="")
    print(f"1: {u.name}")
    if not u.name:
        print(f"2: {u.name}")
        del u.name
    print(
        f"3: {u.name}"
    )  # cause: AttributeError: 'User' object has no attribute 'name'

and they all raise AttributeError maybe something wrong at validate_self method?@roman-right

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 30 days with no activity.

github-actions[bot] commented 1 year ago

This issue was closed because it has been stalled for 14 days with no activity.