Skip to content

Commit 445b94e

Browse files
committed
model: correctly parse Nullable attribute values
As pointed out in the issue: - true or missing attribute means Nullable - false means non Nullable For our convenience when writing tests, Python True/False support added. Closes #98
1 parent cc8c71a commit 445b94e

File tree

5 files changed

+53
-2
lines changed

5 files changed

+53
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1212
### Changed
1313
- handle GET EntitySet payload without the member results - Jakub Filak
1414
- both Literal and JSON DateTimes has Timezone set to UTC - Jakub Filak
15+
- properties without the attribute Nullable accepts null values - Jakub Filak
1516

1617
### Fixed
1718
- removed superfluous debug print when parsing FunctionImports from metadata - Jakub Filak

pyodata/v2/model.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,9 @@ def __init__(self, name, type_info, nullable, max_length, precision, scale):
647647
self._type_info = type_info
648648
self._typ = None
649649

650-
self._nullable = bool(nullable)
650+
self._nullable = nullable is None or \
651+
(isinstance(nullable, str) and nullable.lower() == 'true') or \
652+
(isinstance(nullable, bool) and nullable)
651653

652654
if not max_length:
653655
self._max_length = None

tests/metadata.xml

+3
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@
205205
<FunctionImport Name="retrieve" ReturnType="Edm.Boolean" EntitySet="MasterEntities" m:HttpMethod="GET"
206206
sap:action-for="EXAMPLE_SRV.MasterEntity">
207207
<Parameter Name="Param" Type="Edm.String" Mode="In" MaxLenght="5"/>
208+
<Parameter Name="ParamNonNull" Type="Edm.String" Mode="In" MaxLenght="5" Nullable="false"/>
209+
<Parameter Name="ParamNull" Type="Edm.String" Mode="In" MaxLenght="5" Nullable="true"/>
208210
</FunctionImport>
209211
<FunctionImport Name="refresh" m:HttpMethod="GET"/>
210212
<AssociationSet Name="toDataEntitySet" Association="EXAMPLE_SRV.toDataEntity" sap:creatable="false"
@@ -303,6 +305,7 @@
303305
<Property Name="ID" Type="Edm.Int32" Nullable="false"/>
304306
<Property Name="NameFirst" Type="Edm.String" Nullable="true"/>
305307
<Property Name="NameLast" Type="Edm.String" Nullable="true"/>
308+
<Property Name="NickName" Type="Edm.String"/>
306309
<NavigationProperty Name="Addresses" Relationship="EXAMPLE_SRV_SETS.AssociationEmployeeAddress"
307310
FromRole="EmployeeRole" ToRole="AddressRole"/>
308311
</EntityType>

tests/test_model_v2.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def test_edmx(schema):
8181
assert master_prop_data.text_proprty.name == 'DataName'
8282
assert master_prop_data.visible
8383
assert master_prop_data.max_length == StructTypeProperty.MAXIMUM_LENGTH
84+
assert master_prop_data.nullable == False
8485

8586
master_prop_data_vh = master_prop_data.value_helper
8687
assert str(master_prop_data_vh) == 'ValueHelper(MasterEntity/Data)'
@@ -147,6 +148,19 @@ def test_edmx(schema):
147148
assert typ_ex_info.value.args[0] == 'Type FooBar does not exist in Schema Namespace EXAMPLE_SRV'
148149

149150

151+
def test_schema_entity_type_nullable(schema):
152+
emp_entity = schema.entity_type('Employee')
153+
154+
id_property = emp_entity.proprty('ID')
155+
assert id_property.nullable == False
156+
157+
firstname_property = emp_entity.proprty('NameFirst')
158+
assert firstname_property.nullable == True
159+
160+
nickname_property = emp_entity.proprty('NickName')
161+
assert nickname_property.nullable == True
162+
163+
150164
def test_schema_entity_sets(schema):
151165
"""Test Schema methods for EntitySets"""
152166

@@ -282,12 +296,18 @@ def test_edmx_function_imports(schema):
282296
assert str(param) == 'FunctionImportParameter(Param)'
283297
assert param.name == 'Param'
284298
assert param.typ.name == 'Edm.String'
285-
assert not param.nullable
299+
assert param.nullable
286300
assert param.max_length is None
287301
assert param.mode == 'In'
288302
assert param.typ.traits.to_literal('Foo') == "'Foo'"
289303
assert param.typ.traits.from_literal("'Foo'") == 'Foo'
290304

305+
param_non_null = function_import.parameters[1]
306+
assert param_non_null.nullable == False
307+
308+
param_null = function_import.parameters[2]
309+
assert param_null.nullable == True
310+
291311
# function import without return type
292312
function_import = schema.function_import('refresh')
293313
assert function_import.return_type is None

tests/test_model_v2_VariableDeclaration.py

+25
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,28 @@ def test_variable_of_string_to_literal_none(variable_of_string):
7272
with pytest.raises(PyODataException) as e_info:
7373
variable_of_string.to_literal(None)
7474
assert str(e_info.value).startswith('Cannot convert None to URL literal of VariableDeclaration(TestVariable)')
75+
76+
77+
def test_variable_nullable_init_param_None():
78+
variable = VariableDeclaration('TestVariable', Types.parse_type_name('Edm.String'), None, None, None, None)
79+
assert variable.nullable == True
80+
81+
82+
def test_variable_nullable_init_param_str_true():
83+
variable = VariableDeclaration('TestVariable', Types.parse_type_name('Edm.String'), 'true', None, None, None)
84+
assert variable.nullable == True
85+
86+
87+
def test_variable_nullable_init_param_str_false():
88+
variable = VariableDeclaration('TestVariable', Types.parse_type_name('Edm.String'), 'false', None, None, None)
89+
assert variable.nullable == False
90+
91+
92+
def test_variable_nullable_init_param_bool_true():
93+
variable = VariableDeclaration('TestVariable', Types.parse_type_name('Edm.String'), True, None, None, None)
94+
assert variable.nullable == True
95+
96+
97+
def test_variable_nullable_init_param_bool_false():
98+
variable = VariableDeclaration('TestVariable', Types.parse_type_name('Edm.String'), False, None, None, None)
99+
assert variable.nullable == False

0 commit comments

Comments
 (0)