简体   繁体   中英

How to model a Pydantic Model to accept IP as either dict or as cidr string

In Pydantic, is it possible to pass a value that is not a dict and still make it go through a BaseModel?

I have a case where I want to be able to process a CIDR formatted IP (eg 1.2.3.4/32) and still return a valid model Ipv4.

In the example below I initialize 3 IPs. For the 3rd IP I pass a CIDR formatted str and want to be able to return a valid Ipv4 model.

the @root_validator is only used to print the passed values.

You can see that the 3rd value for key 'ip3' is not processed by the class. The error is

pydantic.error_wrappers.ValidationError: 1 validation error for Ips ip3
value is not a valid dict (type=type_error.dict)


Code

from pydantic import BaseModel, root_validator


class Ipv4(BaseModel):
    """
    Validate structure of IPv4
    """
    address: str
    subnet_mask: int = 22

    @root_validator(pre=True)
    def handle_address_from_cidr_notation(cls, values):
        print(f'These are the values passed into the model: {values}')
        return values

class Ips(BaseModel):
    ip1: Ipv4
    ip2: Ipv4
    ip3: Ipv4




ips_dict = {
    'ip1': {'address': '1.1.1.1', 'subnet_mask': 24},
    'ip2': {'address': '1.1.1.1'},
    'ip3': '1.1.1.1',
}

ips: Ips = Ips(**ips_dict)

print(ips.ip1)
print(ips.ip2)
print(ips.ip3)

Output

These are the values passed into the model: {'address': '1.1.1.1', 'subnet_mask': 24}
These are the values passed into the model: {'address': '1.1.1.1'}
Traceback (most recent call last):
  File "playground/test_pydantic_13.py", line 30, in <module>
    ips: Ips = Ips(**ips_dict)
  File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Ips
ip3
  value is not a valid dict (type=type_error.dict)

This works on Python 3.10 with pydantic 1.9 For Python 3.6 with pydantic 1.9, There is an error when using

Union[Ipv4, Ipv4Cidr]

Model Ipv4Cidr provides a validate method that splits the cidr formatted str to address and su.net_mask and passes the new values to Model Ipv4 which it inherits.

This allows for passing the IP in 3 different ways:

  • ip address + su.net mask
  • ip address only
  • ip address / su.net mask - cidr notation

Code

from typing import Optional, Union

from pydantic import BaseModel



class Ipv4(BaseModel):
    """
    Validate structure of IPv4
    """
    address: str
    subnet_mask: Optional[int]


class Ipv4Cidr(Ipv4):
    """
    Validate structure of IPv4
    """

    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, value: str, field):
        if isinstance(value, str):
            try:
                address, subnet_mask = value.split('/')
                return Ipv4(address=address, subnet_mask=subnet_mask)
            except ValueError as ve:
                return Ipv4(address=value)
        else:
            return Ipv4(**value)



class Ips(BaseModel):
    ip1: Ipv4
    ip2: Union[Ipv4, Ipv4Cidr]
    ip3: Ipv4Cidr
    ip4: Union[Ipv4, Ipv4Cidr]


ips_dict = {
    'ip1': {'address': '1.1.1.1', 'subnet_mask': 24},
    'ip2': {'address': '2.2.2.2'},
    'ip3': '3.3.3.3/32',
    'ip4': '4.4.4.4/32',
}

ips: Ips = Ips(**ips_dict)

print(ips.ip1)
print(ips.ip2)
print(ips.ip3)
print(ips.ip4)

Output for Python 3.10 + Pydantic 1.9

address='1.1.1.1' subnet_mask=24
address='2.2.2.2' subnet_mask=None
address='3.3.3.3' subnet_mask=32
address='4.4.4.4' subnet_mask=32

Output for Python 3.6 + Pydantic 1.9

Traceback (most recent call last):
  File "playground/test_pydantic_13.py", line 50, in <module>
    ips: Ips = Ips(**ips_dict)
  File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Ips
ip4
  value is not a valid dict (type=type_error.dict)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM