Skip to content

Commit a85e935

Browse files
committed
fix when some kind of unions are failed to validate
1 parent c26bb87 commit a85e935

File tree

5 files changed

+340
-11
lines changed

5 files changed

+340
-11
lines changed

datamodel/converters.pyx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,12 @@ cdef object _parse_typing_type(
586586
"""
587587
cdef tuple type_args = getattr(T, '__args__', ())
588588

589+
# print('FIELD > ', field)
590+
# print('T > ', T)
591+
# print('NAME > ', name)
592+
# print('DATA > ', data)
593+
# print('TYPE > ', type_args)
594+
589595
if field.origin in {dict, Mapping} or name in {'Dict', 'Mapping'}:
590596
if isinstance(data, dict):
591597
if type_args:

datamodel/validation.pyx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,20 @@ cpdef bool_t is_optional_type(object annotated_type):
4444
return False
4545

4646
cpdef list _validation(object F, str name, object value, object annotated_type, object val_type, str field_type):
47+
cdef bint _valid = False
48+
4749
if not annotated_type:
4850
annotated_type = F.type
4951
elif isinstance(annotated_type, Field):
5052
annotated_type = annotated_type.type
5153
errors = []
5254

5355
# first: calling (if exists) custom validator:
56+
# print('VALIDATION F ', F)
57+
# print('VALIDATION NAME ', name)
58+
# print('VALIDATION VALUE ', value)
59+
# print('VALIDATION ANNOTATED TYPE ', annotated_type)
60+
5461
fn = F.metadata.get('validator', None)
5562
if fn is not None and callable(fn):
5663
try:
@@ -153,15 +160,19 @@ cpdef list _validation(object F, str name, object value, object annotated_type,
153160
# Otherwise check that value is an instance of at least one inner type:
154161
for t in inner_types:
155162
base_type = get_origin(t) or t
156-
if not isinstance(value, base_type):
157-
errors.append(
158-
_create_error(
159-
name,
160-
value,
161-
f"Invalid type for Optional field; expected one of {inner_types}",
162-
val_type, annotated_type
163-
)
163+
if isinstance(value, base_type):
164+
_valid = True
165+
break
166+
if not _valid:
167+
errors.append(
168+
_create_error(
169+
name,
170+
value,
171+
f'Invalid type for {annotated_type}.{name}, expected a type of {inner_types!s}',
172+
val_type,
173+
annotated_type
164174
)
175+
)
165176
return errors
166177
# elif type(annotated_type) is ModelMeta:
167178
elif type(annotated_type).__name__ == "ModelMeta":

datamodel/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
'simple library based on python +3.8 to use Dataclass-syntax'
77
'for interacting with Data'
88
)
9-
__version__ = '0.8.13'
9+
__version__ = '0.8.14'
1010
__copyright__ = 'Copyright (c) 2020-2024 Jesus Lara'
1111
__author__ = 'Jesus Lara'
1212
__author_email__ = '[email protected]'

examples/test_driver.py

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,125 @@
11
from typing import Union, Optional
2-
from dataclasses import InitVar
2+
from dataclasses import asdict, InitVar
33
from pathlib import Path
44
from datamodel import BaseModel, Field
5+
from datamodel.exceptions import ValidationError
6+
7+
8+
def default_properties() -> tuple:
9+
return ('host', 'port', 'user', 'username', 'password')
10+
11+
class BaseDriver(BaseModel):
12+
"""BaseDriver.
13+
14+
Description: Base class for all required datasources.
15+
"""
16+
driver: str = Field(required=True, primary_key=True)
17+
driver_type: str = Field(
18+
required=True,
19+
default='asyncdb',
20+
comment="type of driver, can be asyncdb, qs or REST"
21+
)
22+
name: str = Field(required=False, comment='Datasource name, default to driver.')
23+
description: str = Field(comment='Datasource Description', repr=False)
24+
icon: str = Field(required=False, comment='Icon Path for Datasource.', repr=False)
25+
dsn: str = Field(default=None)
26+
dsn_format: str = Field(required=False, default=None, repr=False)
27+
user: InitVar = Field(default='')
28+
username: str = Field(default='')
29+
password: str = Field(required=False, default=None, repr=False, is_secret=True)
30+
auth: dict = Field(required=False, default_factory=dict)
31+
required_properties: Optional[tuple] = Field(
32+
repr=False,
33+
default=default_properties(),
34+
default_factory=tuple
35+
)
36+
37+
def __post_init__(self, user, **kwargs) -> None: # pylint: disable=W0613,W0221
38+
if not self.name:
39+
self.name = self.driver
40+
if user:
41+
self.username = user
42+
self.auth = {
43+
"username": self.username,
44+
"password": self.password
45+
}
46+
# set DSN (if needed)
47+
if self.dsn_format is not None and self.dsn is None:
48+
self.create_dsn()
49+
super(BaseDriver, self).__post_init__()
50+
51+
def create_dsn(self) -> str:
52+
"""create_dsn.
53+
54+
Description: creates DSN from DSN Format.
55+
Returns:
56+
str: DSN.
57+
"""
58+
params = asdict(self)
59+
try:
60+
self.dsn = self.dsn_format.format(**params)
61+
return self.dsn
62+
except (AttributeError, ValueError):
63+
return None
64+
65+
def get_credentials(self) -> dict:
66+
"""get_credentials.
67+
68+
Description: Returns credentials for Datasource.
69+
Returns:
70+
dict: credentials.
71+
"""
72+
return self.params()
73+
74+
def get_parameters(self) -> dict:
75+
return {}
76+
77+
@classmethod
78+
def properties(cls) -> dict:
79+
"""properties.
80+
81+
Description: Returns fields related to Drivers Supported.
82+
Returns:
83+
dict: all required fields for Supported Drivers.
84+
"""
85+
86+
fields = {}
87+
for field in cls.required_properties:
88+
# because tuple is ordered:
89+
try:
90+
f = cls.column(cls, field)
91+
except KeyError:
92+
continue # Field Missing on Driver:
93+
secret = False
94+
if 'is_secret' in f.metadata:
95+
secret = f.metadata["is_secret"]
96+
title = field
97+
if 'title' in f.metadata:
98+
title = f.metadata['title']
99+
required = False
100+
if 'required' in f.metadata:
101+
required = f.metadata['required']
102+
f = {
103+
"name": field,
104+
"title": title,
105+
"required": required,
106+
"is_secret": secret
107+
}
108+
value = getattr(cls, field)
109+
default = hasattr(f, 'default')
110+
if not value and default:
111+
value = f.default
112+
if value:
113+
f["value"] = value
114+
fields[field] = f
115+
return {
116+
"driver": cls.driver,
117+
"name": cls.name,
118+
"icon": cls.icon,
119+
"dsn_format": cls.dsn_format,
120+
"fields": fields
121+
}
122+
5123

6124
def jdbc_properties() -> tuple:
7125
return ('host', 'port', 'user', 'password', 'database', 'dsn', 'jar', 'classpath')
@@ -20,7 +138,7 @@ class jdbcDriver(BaseModel):
20138
jar: Union[list, str] = Field(Required=True)
21139
classpath: Path = Field(Required=False)
22140
required_properties: Optional[Union[list, tuple]] = Field(
23-
repr=False, default=jdbc_properties()
141+
repr=False, default=jdbc_properties(), default_factory=tuple
24142
)
25143

26144
def __post_init__(self, username, *args, **kwargs):
@@ -53,9 +171,21 @@ def params(self) -> dict:
53171
jar='/Users/jlara/.m2/repository/com/oracle/ojdbc/ojdbc8/',
54172
classpath='/Users/jlara/.m2/repository/com/oracle/ojdbc/ojdbc8/ojdbc8-'
55173
)
174+
print('JDBC > ', jdbc_default)
56175
except ValueError:
57176
jdbc_default = None
58177
except Exception as e:
59178
print('ERROR > ', e, type(e))
60179
print('PAYLOAD > ', e.payload)
61180
jdbc_default = None
181+
182+
try:
183+
base_driver = BaseDriver(
184+
driver='asyncdb',
185+
user='admin',
186+
password='admin',
187+
name='asyncdb_test'
188+
)
189+
print('BASE DRIVER > ', base_driver)
190+
except ValidationError as e:
191+
print(e.payload)

0 commit comments

Comments
 (0)