Pydantic - Validation Does not Happen - python

I am quite new to using Pydantic.
The Issue I am facing right now is that the Model Below is not raising the Expected Exception when the value is out of range.
For example, if you pass -1 into this model it should ideally raise an HTTPException. but nothing happens
I am not sure where I might be going wrong.
Any Advice would be great.
class GetInput:
"""
for the fields endpoint
"""
def __init__(self,
rank: Optional[int] = None,
interval: Optional[int] = None):
self.rank = rank
self.interval = interval
#validator('rank')
def check_if_rank_in_range(cls, v):
"""
check if input rank is within range
"""
if not 0 < v < 1000001:
raise HTTPException(
status_code=400, detail="Rank Value Must be within range (0,1000000)")
return v
#validator('interval')
def check_if_interval_in_range(cls, v):
"""
check if input rank is within range
"""
if not 0 < v < 1000001:
raise HTTPException(
status_code=400, detail="Interval Value Must be within range (0,1000000)")
return v
The FastAPI Endpoint
#router.get('fields/',status_code=200)
def get_data(params: GetInput = Depends()):
if params.rank:
result = get_info_by_rank(params.rank)
elif params.interval:
result = get_info_by_interval(params.interval)
return result

class GetInput(BaseModel):
rank: Optional[int]=None
interval: Optional[int]=None
#validator("*")
def check_range(cls, v):
if v:
if not 0 < v < 1000001:
raise HTTPException(status_code=400, detail="Value Must be within range (0,1000000)")
return v
the validator was not working due to not Inheriting the BaseModel Class
When the BaseModel Class would get inherited it would throw an error if either of the values is empty thus the additional if statement.

Related

How can I put limits on a variable type int in python?

I need to put a limit when entering a number for a certain attribute, how can I do that?
class Escuelas:
def __init__(self):
self.cod_provincia = 0:1
You should use Encapsulation principle.
class Escuelas:
def __init__(self):
self.__cod_provincia = 0
#property
def cod_provincia(self):
return self.__cod_provincia
#cod_provincia.setter
def cod_provincia(self, value):
if isinstance(value, int) and value in (0, 1):
self.__cod_provincia = value
else:
raise ValueError('Not Valid Code!')
if you want set value in specific range you just use range(start, end) instead of the tuple (0, 1) in setter method.
Maybe somewhere in your code, you need something like below:
if self.cod_provincia not in [0, 1]:
raise ValueError("Entrada de código no válida.")
If I understand you correctly, you want to force an int value to be within a certain range when it is set.
Is this what you require?
class Escuelas:
def __init__(self):
self.intValue = None
self.upperLimit = 100
def setIntValue(self,newValue):
self.intValue = self.upperLimit if newValue>self.upperLimit else newValue
e = Escuelas()
e.setIntValue(200)
print(e.intValue)
This sets the value to 100 (the upper limit) and not 200.

How to limit values in dataclass attribute?

I have the following dataclass Gear that I want to limit the maximum value for gear_level from 0 to 5. But as you can see when I increment gear_level, it goes higher than 5, which is not what I want. I tried method as well as postinit. How do I fix this problem?
from dataclasses import dataclass
#dataclass
class Gear:
gear_level: int = 0
direction: str = None
# more codes ...
def __postinit__(self):
if self.gear_level <= 0:
self.gear_level = 0
elif 5 > self.gear_level > 0:
self.gear_level = self.gear_level
else:
self.gear_level = 5
def set_gear_level(self, level):
if level <= 0:
self.gear_level = 0
elif 5 > level > 0:
self.gear_level = level
else:
self.gear_level = 5
g = Gear()
g.set_gear_level(6)
print(g)
g.gear_level += 1
print(g)
g.set_gear_level(-1)
print(g)
g.gear_level -= 1
print(g)
Ideally, I prefer to use the g.gear_level += 1 notation, because I want to increment gear_level. It should not jump from gear level 1 to 5. Also, when it decrement, it should stop at 0. It should take both an assignment of 0 and be allowed to decrement to 0. Can this be done?
Gear(gear_level=5, direction=None)
Gear(gear_level=6, direction=None)
Gear(gear_level=0, direction=None)
Gear(gear_level=-1, direction=None)
In this case I would simply use a property:
#dataclass
class Gear:
gear_level: int
# Rest of the class excluded for simplicity
#property
def gear_level(self) -> int:
return self._gear_level
#gear_level.setter
def gear_level(self, value: int) -> None:
self._gear_level = min(max(value, 0), 5)
This way you don't need to write a __post_init__ or have to remember to call specific methods: assignment to gear_level will be kept 0 <= gear_level <= 5, even with +=.
The suggested link in the comments provides an elegant solution for tackling this issue, e.g. using a custom descriptor class which should work with minimal changes needed on your end.
For example, here's how I'd define a BoundsValidator descriptor class to check that a class attribute is within an expected lower and upper bounds (note that either bounds are optional in this case):
from typing import Optional
try:
from typing import get_args
except ImportError: # Python 3.7
from typing_extensions import get_args
class BoundsValidator:
"""Descriptor to validate an attribute x remains within a specified bounds.
That is, checks the constraint `low <= x <= high` is satisfied. Note that
both low and high are optional. If none are provided, no bounds will be
applied.
"""
__slots__ = ('name',
'type',
'validator')
def __init__(self, min_val: Optional[int] = None,
max_val: Optional[int] = float('inf')):
if max_val is None: # only minimum
def validator(name, val):
if val < min_val:
raise ValueError(f"values for {name!r} have to be > {min_val}; got {val!r}")
elif min_val is None: # only maximum
def validator(name, val):
if val > max_val:
raise ValueError(f"values for {name!r} have to be < {max_val}; got {val!r}")
else: # both upper and lower bounds are given
def validator(name, val):
if not min_val <= val <= max_val:
raise ValueError(f"values for {name!r} have to be within the range "
f"[{min_val}, {max_val}]; got {val!r}")
self.validator = validator
def __set_name__(self, owner, name):
# save the attribute name on an initial run
self.name = name
# set the valid types based on the annotation for the attribute
# for example, `int` or `Union[int, float]`
tp = owner.__annotations__[name]
self.type = get_args(tp) or tp
def __get__(self, instance, owner):
if not instance:
return self
return instance.__dict__[self.name]
def __delete__(self, instance):
del instance.__dict__[self.name]
def __set__(self, instance, value):
# can be removed if you don't need the type validation
if not isinstance(value, self.type):
raise TypeError(f"{self.name!r} values must be of type {self.type!r}")
# validate that the value is within expected bounds
self.validator(self.name, value)
# finally, set the value on the instance
instance.__dict__[self.name] = value
Finally, here's the sample code I came up with to test that it's working as we'd expect:
from dataclasses import dataclass
from typing import Union
#dataclass
class Person:
age: int = BoundsValidator(1) # let's assume a person must at least be 1 years
num: Union[int, float] = BoundsValidator(-1, 1)
gear_level: int = BoundsValidator(0, 5)
def main():
p = Person(10, 0.7, 5)
print(p)
# should raise a ValueError now
try:
p.gear_level += 1
except ValueError as e:
print(e)
# and likewise here, for the lower bound
try:
p.gear_level -= 7
except ValueError as e:
print(e)
# all these should now raise an error
try:
_ = Person(0, 0, 2)
except ValueError as e:
print(e)
try:
_ = Person(120, -3.1, 2)
except ValueError as e:
print(e)
if __name__ == '__main__':
main()
This provides the output below when we run the code:
Person(age=10, num=0.7, gear_level=5)
values for 'gear_level' have to be within the range [0, 5]; got 6
values for 'gear_level' have to be within the range [0, 5]; got -2
values for 'age' have to be within the range [1, inf]; got 0
values for 'num' have to be within the range [-1, 1]; got -3.1
There is also an excellent library called param that enables you to achieve this rather easily. In your case it would look something like this:
import param
#dataclass
class Gear:
gear_level: int = param.Integer(1, bounds=(0,5))
direction: str = None
g = Gear()
print(g)
g.gear_level = 42 # This throws an exception
It has many other neat features for designing robust interfaces.

AssertEqual failing when comparing two seemingly identical IndexErrors

I have a DynamicArray class shown below. (I have only included relevant methods. The rest can be viewed from https://www.geeksforgeeks.org/implementation-of-dynamic-array-in-python/)
import ctypes
class DynamicArray:
'''
Dynamic Array class
'''
def __init__(self):
self.n = 0 # count number of elements
self.capacity = 1 # default capacity
self.A = self.make_array(self.capacity)
def __len__(self):
"""
Return number of elements in the array
"""
return self.n
def __getitem__(self,k):
"""
Return element at k index
"""
#Check if K index is out of bounds#
if not 0 <= k < self.n:
return IndexError('{} is out of bounds'.format(k))
return self.A[k] #Retrieve from the array at index k#
Then I have another unit test file down below
from DynamicArray import DynamicArray
import unittest
class MyTestCase(unittest.TestCase):
def setUp(self) -> None:
self.a = DynamicArray() # empty array
self.b = DynamicArray()
self.b.append(0)
self.c = DynamicArray()
self.c.append(0)
self.c.append(1)
def test_getitem(self):
self.assertEqual(self.a.__getitem__(0),IndexError('0 is out of bounds'))
When I run the test I expect self.a.__getitem__(0) to throw IndexError('0 is out of bounds') and I can't see why the assertion fails? The only difference is that self.a.__getitem__(0) will yield IndexError('{} is out of bounds'.format(0)), which seems to me the same as IndexError('0 is out of bounds')
I tried running below code to see if the string by themselves were any different
if '{} is out of bounds'.format(0) == '0 is out of bounds':
print('str equal')
if '{} is out of bounds'.format(0).__len__() == '0 is out of bounds'.__len__():
print('len equal')
if IndexError('{} is out of bounds'.format(0)) == IndexError('0 is out of bounds'):
print('IndexError equal')
and confirmed that only the third if statement did not print
below is the photo of the console
Thanks in advance. Constructive criticisms and feedbacks are welcome.
Exceptions can't be conpared with assertEqual.
with self.assertRaises(IndexError, msg='0 is out of bounds'):
self.a[0]
And Exceptions must be raiseed to be captured.
You're returning IndexError
raise IndexError('{} is out of bounds'.format(k))
https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises

How to validate class parameters in __init__?

I've been searching a lot for this type of problem, haven't found a single similar question on SO yet.
I have tried using a for loop to put some validation in the train's init that would disregard those passengers although with no success.
tl;dr - How do I validate which passengers will be in the passengers list when I know that some of the passengers in the passengers list are ineligble to be in the train, due to seat_nr out of range or the carriage_nr out of range.
"""Train."""
class Train:
def __init__(self, passengers: list, carriages: int, seats_in_carriage: int):
#--Insert validation for passengers allowed on train--#
self._passengers = passengers
self._carriages = carriages
self._seats_in_carriage = seats_in_carriage
#property
def passengers(self) -> list:
return self._passengers
#property
def carriages(self) -> int:
return self._carriages
#property
def seats_in_carriage(self) -> int:
return self._seats_in_carriage
def get_seats_in_train(self) -> int:
return self._seats_in_carriage * self._carriages
def get_number_of_passengers(self) -> int:
return len(self._passengers)
def get_passengers_in_carriages(self) -> dict:
return {}
#passengers.setter
def passengers(self, value_list: list):
self._passengers = value_list
#carriages.setter
def carriages(self, value: int):
self._carriages = value
#seats_in_carriage.setter
def seats_in_carriage(self, value: int):
self._seats_in_carriage = value
class Passenger:
def __init__(self, passenger_id: str, seat: str):
self.carriage_number = seat.split("-")[0]
self.seat_number = seat.split("-")[1]
self._seat = seat
self._passenger_id = passenger_id
def __dict__(self):
if str(2) >= self.seat_number > str(0) and self.carriage_number <= str(3):
passenger_dct = {'id': str(self._passenger_id), 'seat': str(self._seat)}
return passenger_dct
if __name__ == '__main__':
passengers = [
Passenger('test', '1-2'), #--"test"= passenger_id, "x-y" : x= carriage_nr, y= seat_nr// valid
Passenger('test2', '2-3'), #-- invalid, seat_nr=3, train carriage has only 2 seats.
Passenger('test3', '4-2'), #-- invalid, carriage_nr = 4, train only has 3 carriages.
Passenger('test4', '3-2'), #-- valid
Passenger('test5', '1-1'), #-- valid
Passenger('test6', '1-0'), #-- invalid, no seat_nr 0 on train carriage
]
assert passengers[0].__dict__() == {'id': 'test', 'seat': '1-2'}
t = Train(passengers, 3, 2) #--passengers list, number of carriages, number of seats in carriage.
print(t.get_number_of_passengers()) # -- Should print 3, instead prints all 6.
Any info regarding to the topic is much appreciated, thanks in advance!
I think the solution is pretty straightforward and I'm not sure why this wouldn't work: "I have tried using a for loop to put some validation in the train's init that would disregard those passengers although with no success." Essentially you can just loop through the passenger list and filter out invalid passenger objects as such:
self._passengers = list(filter(lambda p: p.seat_number is valid, passengers))
This uses a lambda function to check certain criteria for including them in the final list. Also your getters and setters don't currently do anything so they are kind of pointless.

Get setter status in python

I am looking to return a status when setting an instance variable. This return value will either indicate that the value was set or the value was invalid. For example:
class C():
def __init__(self):
self._attr = 1
#property
def attr(self):
return self._attr
#attr.setter
def attr(self, val):
if val < 10:
self._attr = val
return 'value set'
else:
return 'value must be < 10'
status = C().attr = 11 # should return failed
status = C().attr = 9 # should return worked
Adding return values to all my setters would be time-consuming and seems to be bad practice. Is there a better way to get a status from a setter?
The other solution I thought of was to write a "setSetter()" function that calls attr = val then checks if attr == val (which works like a status variable). However, it seems like there should be a better method.
Also, if there is a better way to structure the flow control so that attributes aren't set to invalid values I'm open to changing my strategy.
Thanks in advance for the help!
The standard way to indicate failure in Python and/or prevent code from reaching an invalid state is to raise an exception to indicate you've encountered a situation where you can't continue execution. This doesn't require you to modify the return type:
#attr.setter
def attr(self, val: int) -> None:
if val < 10:
self._attr = val
else:
raise ValueError('value must be < 10')
try:
C().attr = 9 # works
C().attr = 11 # raises ValueError and goes to nearest matching except
C().attr = 13 # is never executed
except ValueError as e:
print(e) # prints 'value must be < 10'

Categories

Resources