keleshev / schema

Schema validation just got Pythonic
MIT License
2.86k stars 214 forks source link

How to translate string to list #302

Open NiceRath opened 11 months ago

NiceRath commented 11 months ago

Greetings!

We have the use-case that some values should be translated to lists if only one value is supplied.

Example:

# nameservers: ['192.168.0.1']
Schema({'nameservers': [AND(str, valid_ip)]})
# WORKS

# nameservers: '192.168.0.1'
Schema({'nameservers': [AND(str, valid_ip)]})
# ERROR: "'192.168.0.1' should be instance of 'list'"

Schema({'nameservers': Use(ensure_list)})
# WORKS, will translate to list - but without value validation..

Schema({'nameservers': And(Use(ensure_list), str, valid_ip)})
# ERROR: "['192.168.0.1'] should be instance of 'str'"

# Referenced functions:
from ipaddress import ip_address, IPv4Address, IPv6Address

def valid_ip6(ip: str) -> bool:
    try:
        return type(ip_address(ip)) is IPv6Address

    except ValueError:
        return False

def valid_ip4(ip: str) -> bool:
    try:
        return type(ip_address(ip)) is IPv4Address

    except ValueError:
        return False

def valid_ip(ip: str) -> bool:
    return valid_ip4(ip) or valid_ip6(ip)

def ensure_list(data: (str, list)) -> list:
    if data is None:
        raise ValueError('Got none-value!')

    if not isinstance(data, list):
        data = [data]

    return data

Is there a clean way to validate the return-values that were translated by calling Use()?

As a workaround we can, of course, create a separate Use function for every different use-case. Per example: Use(list_of_ips)

mutricyl commented 5 months ago

May I suggest the following ?

sch = Schema({
    'nameservers': Or(
        And(str, valid_ip, Use(lambda x: [x])),  # manage list conversion when string is provided
        And([str], lambda x: all([valid_ip(it) for it in x]))  # check that all ip are valid in the list in case of list. Use Const( lambda ... , error='nice error message') to display a usefull error message
    )})

print(sch.validate({'nameservers': ['192.168.0.1']}))
print(sch.validate({'nameservers': ['192.168.0.1', '192.168.0.2']}))
print(sch.validate({'nameservers': '192.168.0.1'}))

Resulting in:

{'nameservers': ['192.168.0.1']}
{'nameservers': ['192.168.0.1', '192.168.0.2']}
{'nameservers': ['192.168.0.1']}

Would it fit your needs ?