代码之家  ›  专栏  ›  技术社区  ›  Daniel Artamonov

从额外类型(PhoneNumber)更改自定义类型的逻辑

  •  0
  • Daniel Artamonov  · 技术社区  · 1 年前

    有PhoneNumber类型可以验证电话号码:

    from pydantic import BaseModel
    
    from pydantic_extra_types.phone_numbers import PhoneNumber
    
    
    class User(BaseModel):
        name: str
        phone_number: PhoneNumber
    
    
    user = User(name='John', phone_number='+447911123456')
    print(user.phone_number)  
    #> tel:+44-7911-123456
    

    除了不需要的“tel:”前缀外,我对它很满意。我使用了这样的技巧:

    from pydantic import BaseModel
    
    from pydantic_extra_types.phone_numbers import PhoneNumber
    
    
    class User(BaseModel):
        name: str
        phone_number: PhoneNumber
    
        @model_validator(mode="after")
        def remove_prefix_from_phone(self) -> "User":
            if self.phone_number:
                self.phone_number = self.phone_number.removeprefix("tel:")
            return self
    
    
    user = User(name='John', phone_number='+447911123456')
    print(user.phone_number)  
    #> +44-7911-123456
    

    但问题是,我将字段转换为str,PhoneNumber类甚至不像EmailStr那样被视为str子类型。这似乎是一个糟糕的举动,MyPy将其标记为错误以及分配中不兼容的类型(同时,验证逻辑很好)。
    也许我可以做得更聪明?

    我想不出其他选择,但肯定应该在Pydantic模型中完成,因为我在web服务中从DB加载数据,前缀将存在于自动生成的响应中。

    我很乐意得到任何建议!

    1 回复  |  直到 1 年前
        1
  •  4
  •   OysterShucker    1 年前

    这个 PhoneNumber 类具有以下类变量:

    #https://github.com/pydantic/pydantic-extra-types/blob/092251d226edcf4e06bbe4f904da177fad20a6de/pydantic_extra_types/phone_numbers.py#L26
    
    default_region_code: str | None = None
    phone_format: str = 'RFC3966'
    min_length: int = 7
    max_length: int = 64
    

    如果我们遵循一堆代码链,我们最终会陷入 phonenumber 存储库 电话号码 取决于。这是数据返回之前的最后一站 电话号码 ,我们可以看到 phone_format 最终会通过以下方式影响电话号码:

    #https://github.com/daviddrysdale/python-phonenumbers/blob/2f06ef6db2ca83f3856fbb8019a0c665f5971b13/python/phonenumbers/phonenumberutil.py#L1726
    
    def _prefix_number_with_country_calling_code(country_code, num_format, formatted_number):
        """A helper function that is used by format_number and format_by_pattern."""
        if num_format == PhoneNumberFormat.E164:
            return _PLUS_SIGN + unicod(country_code) + formatted_number
        elif num_format == PhoneNumberFormat.INTERNATIONAL:
            return _PLUS_SIGN + unicod(country_code) + U_SPACE + formatted_number
        elif num_format == PhoneNumberFormat.RFC3966:
            #_RF3966_PREFIX = 'tel:'
            return _RFC3966_PREFIX + _PLUS_SIGN + unicod(country_code) + U_DASH + formatted_number
        else:
            return formatted_number
    
    

    值得注意的是,如果您使用无法识别的格式,则上述内容仅返回格式化的数字。奇怪的是,在上述条件中没有“国家”格式。使用它应该会触发 else .

    这应该能解决你的问题。

    from pydantic import BaseModel
    from pydantic_extra_types.phone_numbers import PhoneNumber
    
    PhoneNumber.phone_format = 'E164' #'INTERNATIONAL', 'NATIONAL'
    
    class User(BaseModel):
        name: str
        phone_number: PhoneNumber
    
    
        2
  •  1
  •   npk    1 年前

    Pydantic尚不支持此功能: Source

    OysterShucker s solution 当您必须为电话号码类型使用多种格式或区域时,这不是最佳选择。

    我使用的解决方法是:

    from pydantic_extra_types import PhoneNumber as PydanticPhoneNumber
    ...
    PhoneNumber = type(
        "PhoneNumberStr",
        (PydanticPhoneNumber,),
        {   
            "default_region_code": "IN",
            "phone_format": "E164",
        },
    )
    

    这与以下内容相同:

    class PhoneNumber(PydanticPhoneNumber):
        default_region_code="IN"
        phone_format="E164"
    

    您还可以编写一个函数,通过将上述任何方法封装在函数中来进一步自定义它。