Skip to content

Commit 2856559

Browse files
committed
feat: add bytes decoding, tests
1 parent dadfd14 commit 2856559

File tree

4 files changed

+40
-5
lines changed

4 files changed

+40
-5
lines changed

src/DIRAC/ConfigurationSystem/Client/ConfigurationClient.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def getCompressedData(self):
2929
:rtype: str
3030
"""
3131
retVal = self.executeRPC("getCompressedData")
32-
if retVal["OK"]:
32+
if retVal["OK"] and isinstance(retVal["Value"], six.string_types):
3333
retVal["Value"] = b64decode(retVal["Value"])
3434
return retVal
3535

@@ -41,7 +41,7 @@ def getCompressedDataIfNewer(self, sClientVersion):
4141
:returns: Configuration data, if changed, compressed
4242
"""
4343
retVal = self.executeRPC("getCompressedDataIfNewer", sClientVersion)
44-
if retVal["OK"] and "data" in retVal["Value"]:
44+
if retVal["OK"] and isinstance(retVal["Value"].get("data"), six.string_types):
4545
retVal["Value"]["data"] = b64decode(retVal["Value"]["data"])
4646
return retVal
4747

src/DIRAC/ConfigurationSystem/Service/TornadoConfigurationHandler.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
__RCSID__ = "$Id$"
1414

15+
import six
1516
from base64 import b64encode, b64decode
1617

1718
from DIRAC import S_OK, S_ERROR, gLogger
@@ -79,7 +80,9 @@ def export_commitNewData(self, sData):
7980
credDict = self.getRemoteCredentials()
8081
if "DN" not in credDict or "username" not in credDict:
8182
return S_ERROR("You must be authenticated!")
82-
sData = b64decode(sData)
83+
# TODO: in 8.0 remove it
84+
if isinstance(sData, six.string_types):
85+
sData = b64decode(sData)
8386
return self.ServiceInterface.updateConfiguration(sData, credDict["username"])
8487

8588
def export_writeEnabled(self):

src/DIRAC/Core/Utilities/JEncode.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from __future__ import division
55
from __future__ import print_function
66

7+
import six
8+
from base64 import b64encode, b64decode
79
import datetime
810
import json
911

@@ -88,6 +90,21 @@ class DJSONEncoder(json.JSONEncoder):
8890
tuple, datetime, and any object inheriting from JSerializable
8991
"""
9092

93+
# TODO: remove it in 8.0
94+
def encode(self, obj):
95+
"""This method for backward compatability with python 2 code"""
96+
if six.PY2:
97+
# Json can't distinguish bytes or str in python 2, so it's trying to encode with utf-8/ascii
98+
try:
99+
# Let's leave this conversion for backward compatibility, although
100+
# it does not guarantee the return of the same data when decoding
101+
return super(DJSONEncoder, self).encode(obj)
102+
except UnicodeDecodeError as e:
103+
# If the encoding type does not match we have an exception
104+
# that json does not try to fix using default method
105+
obj = self.default(obj) # Let's use base64 in this case
106+
return super(DJSONEncoder, self).encode(obj)
107+
91108
def default(self, obj): # pylint: disable=method-hidden
92109
"""Add supports for datetime and JSerializable class to default json
93110
@@ -105,7 +122,9 @@ def default(self, obj): # pylint: disable=method-hidden
105122
# if the object inherits from JSJerializable, try to serialize it
106123
elif isinstance(obj, JSerializable):
107124
return obj._toJSON() # pylint: disable=protected-access
108-
125+
# if the object a bytes, decode it
126+
elif isinstance(obj, bytes):
127+
return {"__dCls": "b64", "obj": b64encode(obj).decode()}
109128
# otherwise, let the parent do
110129
return super(DJSONEncoder, self).default(obj)
111130

@@ -140,6 +159,8 @@ def dict_to_object(dataDict):
140159
return datetime.datetime.strptime(dataDict["obj"], DATETIME_DEFAULT_FORMAT)
141160
elif className == "date":
142161
return datetime.datetime.strptime(dataDict["obj"], DATETIME_DEFAULT_DATE_FORMAT).date()
162+
elif className == "b64":
163+
return b64decode(dataDict["obj"])
143164
elif className:
144165
import importlib
145166

src/DIRAC/Core/Utilities/test/Test_Encode.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from hypothesis import given, settings, HealthCheck
2323
from hypothesis.strategies import (
24+
binary,
2425
builds,
2526
integers,
2627
lists,
@@ -59,7 +60,7 @@
5960
"jsonTuple",
6061
"mixTuple",
6162
"mixTuple (DIRAC_USE_JSON_DECODE=Yes)",
62-
"mixTuple (DIRAC_USE_JSON_ENCODE=Yes",
63+
"mixTuple (DIRAC_USE_JSON_ENCODE=Yes)",
6364
)
6465

6566
enc_dec_imp_without_json = (disetTuple, (mixTuple, "No", "No"), (mixTuple, "Yes", "No"))
@@ -118,6 +119,8 @@ def test_everyBaseTypeIsTested():
118119
for encodeFunc in g_dEncodeFunctions.values():
119120
testFuncName = ("test_BaseType_%s" % encodeFunc.__name__).replace("encode", "")
120121
globals()[testFuncName]
122+
# We forgot about binary types
123+
globals()["test_BaseType_Bytes"]
121124

122125

123126
def agnosticTestFunction(enc_dec_tuple, data):
@@ -167,6 +170,14 @@ def enc_dec_without_json(request, monkeypatch):
167170
return base_enc_dec(request, monkeypatch)
168171

169172

173+
@settings(suppress_health_check=function_scoped)
174+
@given(data=binary())
175+
def test_BaseType_Bytes(data):
176+
"""Test for bytes. Test JEncode with python 3 ONLY, since DEncode distorts the result"""
177+
if six.PY3:
178+
agnosticTestFunction(jsonTuple, data)
179+
180+
170181
@settings(suppress_health_check=function_scoped)
171182
@given(data=booleans())
172183
def test_BaseType_Bool(enc_dec, data):

0 commit comments

Comments
 (0)