33import re
44from typing import Any , Dict , Optional , Type , TypeVar
55
6+
7+ class ValidationError (ValueError ):
8+ """Exception raised for validation errors in the ProjectX SDK."""
9+
10+ pass
11+
12+
613T = TypeVar ("T" )
714
815
@@ -18,10 +25,10 @@ def validate_not_none(value: Optional[Any], name: str) -> Any:
1825 The validated value
1926
2027 Raises:
21- ValueError : If the value is None
28+ ValidationError : If the value is None
2229 """
2330 if value is None :
24- raise ValueError (f"{ name } must not be None" )
31+ raise ValidationError (f"{ name } must not be None" )
2532 return value
2633
2734
@@ -41,19 +48,37 @@ def validate_int_range(
4148 The validated integer
4249
4350 Raises:
44- ValueError : If the value is outside the specified range
51+ ValidationError : If the value is outside the specified range
4552 """
46- validate_not_none (value , name )
53+ if value is None :
54+ raise ValidationError (f"{ name } cannot be None" )
4755
4856 if min_value is not None and value < min_value :
49- raise ValueError (f"{ name } must be at least { min_value } " )
57+ raise ValidationError (f"{ name } must be at least { min_value } " )
5058
5159 if max_value is not None and value > max_value :
52- raise ValueError (f"{ name } must be at most { max_value } " )
60+ raise ValidationError (f"{ name } must be at most { max_value } " )
5361
5462 return value
5563
5664
65+ def validate_non_negative (value : int , name : str ) -> int :
66+ """
67+ Validate that an integer is non-negative (>= 0).
68+
69+ Args:
70+ value: The integer to validate
71+ name: The name of the parameter (for error message)
72+
73+ Returns:
74+ The validated integer
75+
76+ Raises:
77+ ValidationError: If the value is negative
78+ """
79+ return validate_int_range (value , name , min_value = 0 )
80+
81+
5782def validate_string_not_empty (value : Optional [str ], name : str ) -> str :
5883 """
5984 Validate that a string is not empty.
@@ -66,12 +91,12 @@ def validate_string_not_empty(value: Optional[str], name: str) -> str:
6691 The validated string
6792
6893 Raises:
69- ValueError : If the string is None or empty
94+ ValidationError : If the string is None or empty
7095 """
7196 validate_not_none (value , name )
7297
7398 if not value :
74- raise ValueError (f"{ name } must not be empty" )
99+ raise ValidationError (f"{ name } must not be empty" )
75100
76101 return value
77102
@@ -87,15 +112,19 @@ def validate_contract_id_format(contract_id: str) -> str:
87112 The validated contract ID
88113
89114 Raises:
90- ValueError : If the contract ID has an invalid format
115+ ValidationError : If the contract ID has an invalid format
91116 """
92- validate_string_not_empty (contract_id , "contract_id" )
117+ if contract_id is None :
118+ raise ValidationError ("Contract ID cannot be None or empty" )
119+
120+ if not contract_id :
121+ raise ValidationError ("contract_id must not be empty" )
93122
94123 # Example pattern for contract IDs: "CON.F.US.EP.H24"
95124 pattern = r"^CON\.[A-Z]\.[A-Z]{2}\.[A-Z0-9]{1,5}\.[A-Z0-9]{1,5}$"
96125
97126 if not re .match (pattern , contract_id ):
98- raise ValueError (
127+ raise ValidationError (
99128 f"Invalid contract ID format: { contract_id } . "
100129 "Expected format: CON.<type>.<region>.<symbol>.<month/year>"
101130 )
@@ -115,9 +144,15 @@ def validate_model(value: Dict[str, Any], model_class: Type[T]) -> T:
115144 An instance of the model
116145
117146 Raises:
118- ValueError : If the dictionary cannot be converted to the model
147+ ValidationError : If the dictionary cannot be converted to the model
119148 """
120149 try :
121- return model_class .model_validate (value )
150+ # Try model_validate (Pydantic v2) first, then parse_obj (Pydantic v1)
151+ if hasattr (model_class , "model_validate" ):
152+ result : T = model_class .model_validate (value ) # type: ignore
153+ return result
154+ else :
155+ result : T = model_class .parse_obj (value ) # type: ignore
156+ return result
122157 except Exception as e :
123- raise ValueError (f"Invalid { model_class .__name__ } data: { e } " )
158+ raise ValidationError (f"Invalid { model_class .__name__ } data: { e } " )
0 commit comments