0002-caluire-axel-boolean-types-53825.patch
passerelle/contrib/caluire_axel/schemas.py | ||
---|---|---|
17 | 17 |
import copy |
18 | 18 |
import os |
19 | 19 | |
20 |
import xmlschema |
|
21 | ||
20 | 22 |
from passerelle.contrib.utils import axel |
21 | 23 | |
22 | 24 |
BASE_XSD_PATH = os.path.join(os.path.dirname(__file__), 'xsd') |
23 | 25 | |
24 | 26 | |
27 |
boolean_type = { |
|
28 |
'oneOf': [ |
|
29 |
{'type': 'boolean'}, |
|
30 |
{ |
|
31 |
'type': 'string', |
|
32 |
'pattern': '[Oo]|[Nn]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|1|0', |
|
33 |
}, |
|
34 |
] |
|
35 |
} |
|
36 | ||
37 | ||
38 |
def encode_bool(obj): |
|
39 |
if obj is True or str(obj).lower() in ['true', 'o', '1']: |
|
40 |
return 'O' |
|
41 |
if obj is False or str(obj).lower() in ['false', 'n', '0']: |
|
42 |
return 'N' |
|
43 |
return obj |
|
44 | ||
45 | ||
46 |
class CaluireAxelSchema(axel.AxelSchema): |
|
47 |
type_map = { |
|
48 |
'{urn:AllAxelTypes}DATEREQUIREDType': 'date', |
|
49 |
'{urn:AllAxelTypes}DATEType': 'date_optional', |
|
50 |
'{urn:AllAxelTypes}ONType': 'bool', |
|
51 |
'{urn:AllAxelTypes}ONEmptyType': 'bool_optional', |
|
52 |
} |
|
53 | ||
54 |
@classmethod |
|
55 |
def schema_bool(cls): |
|
56 |
return copy.deepcopy(boolean_type) |
|
57 | ||
58 |
def encode_bool(self, obj): |
|
59 |
return encode_bool(obj) |
|
60 | ||
61 |
def decode_bool(self, data): |
|
62 |
value = False |
|
63 |
if data.text.lower() == 'o': |
|
64 |
value = True |
|
65 |
return xmlschema.ElementData( |
|
66 |
tag=data.tag, text=value, content=data.content, attributes=data.attributes |
|
67 |
) |
|
68 | ||
69 | ||
25 | 70 |
class Operation(axel.Operation): |
26 | 71 |
base_xsd_path = BASE_XSD_PATH |
72 |
axel_schema = CaluireAxelSchema |
|
27 | 73 | |
28 | 74 | |
29 | 75 |
find_individus = Operation('FindIndividus') |
passerelle/contrib/caluire_axel/xsd/Individu.xsd | ||
---|---|---|
54 | 54 |
<xsd:element name="EMPLOYEUR" type="all:FOOBARType" minOccurs="0" maxOccurs="1"/><!-- CUSTOM --> |
55 | 55 |
<xsd:element name="VILLEEMP" type="all:FOOBARType" minOccurs="0" maxOccurs="1"/><!-- CUSTOM --> |
56 | 56 |
<xsd:element name="REGIME1" type="all:FOOBARType" minOccurs="0" maxOccurs="1"/><!-- CUSTOM --> |
57 |
<xsd:element name="PAI" type="all:FOOBARType" minOccurs="0" maxOccurs="1"/><!-- CUSTOM -->
|
|
58 |
<xsd:element name="GARDEALTERNEE" type="all:FOOBARType"/><!-- CUSTOM -->
|
|
57 |
<xsd:element name="PAI" type="all:ONEmptyType" minOccurs="0" maxOccurs="1"/><!-- CUSTOM -->
|
|
58 |
<xsd:element name="GARDEALTERNEE" type="all:ONEmptyType"/><!-- CUSTOM -->
|
|
59 | 59 |
<xsd:element name="ADRESSE" type="adr:ADRESSEType" minOccurs="0" maxOccurs="1"/> |
60 | 60 |
<xsd:element name="FAMILLE" type="ind:FAMILLEType" minOccurs="0" maxOccurs="unbounded"/><!-- CUSTOM --> |
61 | 61 |
</xsd:sequence> |
passerelle/contrib/utils/axel.py | ||
---|---|---|
186 | 186 |
return self.decode_bool(data) |
187 | 187 | |
188 | 188 | |
189 |
def xml_schema_converter(base_xsd_path, name, root_element): |
|
189 |
def xml_schema_converter(axel_schema, base_xsd_path, name, root_element):
|
|
190 | 190 |
xsd_path = os.path.join(base_xsd_path, name) |
191 | 191 |
if not os.path.exists(xsd_path): |
192 | 192 |
return None |
193 |
return AxelSchema(xsd_path, root_element)
|
|
193 |
return axel_schema(xsd_path, root_element)
|
|
194 | 194 | |
195 | 195 | |
196 | 196 |
OperationResult = namedtuple('OperationResult', ['json_response', 'xml_request', 'xml_response']) |
... | ... | |
199 | 199 |
class Operation(object): |
200 | 200 |
base_xsd_path = None |
201 | 201 |
default_prefix = '' |
202 |
axel_schema = AxelSchema |
|
202 | 203 | |
203 | 204 |
def __init__(self, operation, prefix=None, request_root_element='PORTAIL'): |
204 | 205 |
if prefix is None: |
205 | 206 |
prefix = self.default_prefix |
206 | 207 |
self.operation = operation |
207 | 208 |
self.request_converter = xml_schema_converter( |
208 |
self.base_xsd_path, '%sQ_%s.xsd' % (prefix, operation), request_root_element |
|
209 |
self.axel_schema, self.base_xsd_path, '%sQ_%s.xsd' % (prefix, operation), request_root_element
|
|
209 | 210 |
) |
210 | 211 |
self.response_converter = xml_schema_converter( |
211 |
self.base_xsd_path, '%sR_%s.xsd' % (prefix, operation), 'PORTAILSERVICE' |
|
212 |
self.axel_schema, self.base_xsd_path, '%sR_%s.xsd' % (prefix, operation), 'PORTAILSERVICE'
|
|
212 | 213 |
) |
213 | 214 |
self.name = re.sub( |
214 | 215 |
'(.?)([A-Z])', lambda s: s.group(1) + ('-' if s.group(1) else '') + s.group(2).lower(), operation |
tests/test_caluire_axel_schema.py | ||
---|---|---|
1 |
# passerelle - uniform access to multiple data sources and services |
|
2 |
# Copyright (C) 2021 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import os |
|
18 | ||
19 |
import pytest |
|
20 |
import xmlschema |
|
21 | ||
22 |
from passerelle.contrib.caluire_axel.schemas import CaluireAxelSchema |
|
23 | ||
24 |
XSD_BASE_DIR = os.path.join( |
|
25 |
os.path.dirname(os.path.abspath(__file__)), '../passerelle/contrib/caluire_axel/xsd' |
|
26 |
) |
|
27 | ||
28 | ||
29 |
@pytest.mark.parametrize('bool_type', ['ONType', 'ONEmptyType']) |
|
30 |
@pytest.mark.parametrize( |
|
31 |
'value, expected, py_expected', |
|
32 |
[ |
|
33 |
('O', 'O', True), |
|
34 |
('o', 'O', True), |
|
35 |
('TRUE', 'O', True), |
|
36 |
('true', 'O', True), |
|
37 |
('True', 'O', True), |
|
38 |
(True, 'O', True), |
|
39 |
('1', 'O', True), |
|
40 |
('N', 'N', False), |
|
41 |
('n', 'N', False), |
|
42 |
('FALSE', 'N', False), |
|
43 |
('false', 'N', False), |
|
44 |
('False', 'N', False), |
|
45 |
(False, 'N', False), |
|
46 |
('0', 'N', False), |
|
47 |
('FOOBAR', 'FOOBAR', None), |
|
48 |
('42', '42', None), |
|
49 |
('OUIFOOBAR', 'OUIFOOBAR', None), |
|
50 |
('FOONONBAR', 'FOONONBAR', None), |
|
51 |
], |
|
52 |
) |
|
53 |
def test_bool_mapping(bool_type, value, expected, py_expected): |
|
54 |
if expected == '' and bool_type == 'ONType': |
|
55 |
# required, can't be empty |
|
56 |
return |
|
57 | ||
58 |
xsd = """<?xml version="1.0" encoding="utf-8" ?> |
|
59 |
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:all="urn:AllAxelTypes"> |
|
60 |
<xsd:import schemaLocation="{path}/AllAxelTypes.xsd" namespace="urn:AllAxelTypes" /> |
|
61 |
<xsd:complexType name="PORTAILType"> |
|
62 |
<xsd:sequence> |
|
63 |
<xsd:element name="BOOL" type="all:{bool_type}"/> |
|
64 |
</xsd:sequence> |
|
65 |
</xsd:complexType> |
|
66 |
<xsd:element name="PORTAIL" type="PORTAILType"/> |
|
67 |
</xsd:schema>""".format( |
|
68 |
path=XSD_BASE_DIR, bool_type=bool_type |
|
69 |
) |
|
70 | ||
71 |
schema = CaluireAxelSchema(xsd, 'PORTAIL') |
|
72 |
xml_data = schema.encode({'PORTAIL': {'BOOL': value}}) |
|
73 |
assert xml_data.find('BOOL').text == expected |
|
74 | ||
75 |
if py_expected is None: |
|
76 |
with pytest.raises(xmlschema.XMLSchemaValidationError): |
|
77 |
schema.decode(xml_data) |
|
78 |
else: |
|
79 |
json_data = schema.decode(xml_data) |
|
80 |
assert json_data['BOOL'] is py_expected |
|
0 |
- |