5

In Python 3, I want to limit the permitted values that are passed to this method:

my_request(protocol_type, url)

Using type hinting I can write:

my_request(protocol_type: str, url: str)

so the protocol and url are limited to strings, but how can I validate that protocol_type accepts only limited set of values, e.g. 'http' and 'https'?

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
RaamEE
  • 3,017
  • 4
  • 33
  • 53
  • Does this answer your question? [Type hint for a function that returns only a specific set of values](https://stackoverflow.com/questions/39398138/type-hint-for-a-function-that-returns-only-a-specific-set-of-values) – Georgy Aug 21 '20 at 09:32

6 Answers6

10

One way is to write code in the method to validate that the value passed in is 'http' or 'https', something in the lines of:

if (protocol_type == 'http') or (protocol_type == 'https'):
  Do Something
else:
  Throw an exception

Which will work fine during runtime, but doesn't provide an indication of a problem while writing the code.

This is why I prefer using Enum and the type-hinting mechanism that Pycharm and mypy implement.

For the code example below you will get a warning in Pycharm from its code-inspection, see attached screenshot. The screenshot shows that if you enter a value that is not enum you will get the "Expected Type:..." warning.

Code:

"""Test of ENUM"""

from enum import Enum


class ProtocolEnum(Enum):
    """
    ENUM to hold the allowed values for protocol
    """
    HTTP: str = 'http'
    HTTPS: str = 'https'


def try_protocol_enum(protocol: ProtocolEnum) -> None:
    """
    Test of ProtocolEnum
    :rtype: None
    :param protocol: a ProtocolEnum value allows for HTTP or HTTPS only
    :return:
    """
    print(type(protocol))
    print(protocol.value)
    print(protocol.name)


try_protocol_enum(ProtocolEnum.HTTP)

try_protocol_enum('https')

Output:

<enum 'ProtocolEnum'>
http
HTTP

Warnings issued by Pycharm Static Code Analysis - Code Inspection

RaamEE
  • 3,017
  • 4
  • 33
  • 53
1

I guess you can use decorators, I have a similar situation but I wanted to validate the parameter types:

def accepts(*types):
    """
    Enforce parameter types for function
    Modified from https://stackoverflow.com/questions/15299878/how-to-use-python-decorators-to-check-function-arguments
    :param types: int, (int,float), if False, None or [] will be skipped
    """
    def check_accepts(f):
        def new_f(*args, **kwds):
            for (a, t) in zip(args, types):
                if t:
                    assert isinstance(a, t), \
                           "arg %r does not match %s" % (a, t)
            return f(*args, **kwds)
        new_f.func_name = f.__name__
        return new_f
    return check_accepts

And then use as:

@accepts(Decimal)
def calculate_price(monthly_item_price):
    ...

You can modify my decorator to achieve what you want.

James Lin
  • 25,028
  • 36
  • 133
  • 233
1

You can just check if the input is correct in the function:

def my_request(protocol_type: str, url: str):
    if protocol_type in ('http', 'https'):
        # Do x
    else:
        return 'Invalid Input'  # or raise an error
Alec
  • 8,529
  • 8
  • 37
  • 63
1

Why not use a Literal for the method argument?

def my_request(protocol_type: Literal["http","https"], url: str):
user1451104
  • 113
  • 1
  • 7
0

Use an if statement that raises an exception if protocol_type isn't in a list of allowed values :

allowed_protocols = ['http', 'https']
if protocol_type not in allowed_protocols:
    raise ValueError()
MaximGi
  • 543
  • 3
  • 12
0

Late answer but hope it can help someone else!

from enum import Enum
from typing import Type, TypeVar, Union

class ProtocolType(Enum):
    HTTP = "http"
    HTTPS = "https"

T = TypeVar("T")

def validate_enum(value: T, enum_type: Type[Enum]) -> T:
    if not any(value == item.value for item in enum_type):
        raise ValueError(f"Invalid value: {value}")
    return value

def my_request(protocol_type: Union[ProtocolType, str], url: str):
    protocol_type = validate_enum(protocol_type, ProtocolType)
    
    print(f"Making a {protocol_type} request to {url}")

# valid calls
my_request("http", "example.com")
my_request("https", "example.com")

# invalid call
my_request("ftp", "example.com")

In this example, we've defined a validate_enum function that takes an enum value and type as arguments. The function checks if the given value matches one of the valid values in the enum. If so, it returns the value, otherwise it throws ValueError.

Then in my_request function we use this validate_enum function to validate the value of protocol_type as ProtocolType enum. This ensures that the value passed to the function is either a member of the enum or a valid string.

This allows you to have validation at the function definition level.

Rayan
  • 31
  • 2