Skip to content

Commit e899d67

Browse files
committed
fix(Client-and-Server): Time fields validation fix, for server and client
ABAP date and time fields correspond to either Python string or datetime objects and the configuration which type to use was missing in PyRFC server. The configuration is added for server with default to "strings", the same as for client. The time string plausibility check is now disabled both for client and server, when strings are used, because some ABAP applications may return "240000" time string. Close #336 Closes #336
1 parent 2c0432b commit e899d67

5 files changed

Lines changed: 139 additions & 53 deletions

File tree

examples/server/server_pyrfc_thread.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ def my_stfc_structure(request_context=None, IMPORTSTRUCT=None, RFCTABLE=None):
1414
IMPORTSTRUCT = {}
1515
if RFCTABLE is None:
1616
RFCTABLE = []
17-
ECHOSTRUCT = IMPORTSTRUCT
17+
ECHOSTRUCT = IMPORTSTRUCT.copy()
18+
ECHOSTRUCT['RFCINT1'] += 1
19+
ECHOSTRUCT['RFCINT2'] += 1
20+
ECHOSTRUCT['RFCINT4'] += 1
1821
if len(RFCTABLE) == 0:
1922
RFCTABLE = [ECHOSTRUCT]
20-
RESPTEXT = f"Python server response: {ECHOSTRUCT['RFCINT1']}, table rows: {len(RFCTABLE)}"
23+
RESPTEXT = f"Python server sends {len(RFCTABLE)} table rows"
2124
print(f"ECHOSTRUCT: {ECHOSTRUCT}")
2225
print(f"RFCTABLE: {RFCTABLE}")
2326
print(f"RESPTEXT: {RESPTEXT}")

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
"-Wno-unused-function",
164164
"-Wno-nullability-completeness",
165165
"-Wno-expansion-to-defined",
166+
"-Wno-unreachable-code",
166167
"-Wno-unreachable-code-fallthrough",
167168
]
168169
LINK_ARGS = [

src/pyrfc/_cyrfc.pyx

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,8 @@ cdef class Connection:
513513
* ``dtime``
514514
ABAP DATE and TIME strings are returned as Python datetime date and time objects,
515515
instead of ABAP date and time strings (default is False)
516+
The plausiblity of time string sent to function container is checked in PyRFC only
517+
if this option set to True. Otherwise validated by SAP NW RFC SDK and ABAP application
516518
517519
* ``rstrip``
518520
right strips strings returned from RFC call (default is True)
@@ -548,7 +550,7 @@ cdef class Connection:
548550
:raises: :exc:`~pyrfc.RFCError` or a subclass
549551
thereof if the connection attempt fails.
550552
"""
551-
cdef unsigned __bconfig
553+
cdef unsigned bconfig
552554
cdef public dict __config
553555
cdef bint active_transaction
554556
cdef bint active_unit
@@ -609,13 +611,13 @@ cdef class Connection:
609611
self.__config['timeout'] = config.get('timeout', None)
610612

611613
# set internal configuration
612-
self.__bconfig = 0
614+
self.bconfig = 0
613615
if self.__config['dtime']:
614-
self.__bconfig |= _MASK_DTIME
616+
self.bconfig |= _MASK_DTIME
615617
if self.__config['return_import_params']:
616-
self.__bconfig |= _MASK_RETURN_IMPORT_PARAMS
618+
self.bconfig |= _MASK_RETURN_IMPORT_PARAMS
617619
if self.__config['rstrip']:
618-
self.__bconfig |= _MASK_RSTRIP
620+
self.bconfig |= _MASK_RSTRIP
619621

620622
self._connection = ConnectionParameters(**params)
621623
self._handle = NULL
@@ -913,7 +915,7 @@ cdef class Connection:
913915
cancel_timer = Timer(timeout, cancel_connection, (self,))
914916
cancel_timer.start()
915917
for name, value in params.iteritems():
916-
fillFunctionParameter(funcDesc, funcCont, name, value)
918+
functionContainerSet(funcDesc, funcCont, name, value, self.bconfig)
917919
# save old handle for troubleshooting
918920
with nogil:
919921
rc = RfcInvoke(self._handle, funcCont, &errorInfo)
@@ -941,10 +943,10 @@ cdef class Connection:
941943
elif errorInfo.code == RFC_CANCELED:
942944
errorInfo.message = fillString(f"Connection was canceled: {closed_handle}. New handle: {self.handle}")
943945
self._error(&errorInfo)
944-
if self.__bconfig & _MASK_RETURN_IMPORT_PARAMS:
945-
return wrapResult(funcDesc, funcCont, <RFC_DIRECTION> 0, self.__bconfig)
946+
if self.bconfig & _MASK_RETURN_IMPORT_PARAMS:
947+
return functionContainerGet(funcDesc, funcCont, <RFC_DIRECTION> 0, self.bconfig)
946948
else:
947-
return wrapResult(funcDesc, funcCont, RFC_IMPORT, self.__bconfig)
949+
return functionContainerGet(funcDesc, funcCont, RFC_IMPORT, self.bconfig)
948950
finally:
949951
RfcDestroyFunction(funcCont, NULL)
950952

@@ -1061,7 +1063,7 @@ cdef class Connection:
10611063
self._error(&errorInfo)
10621064
try:
10631065
for name, value in params.iteritems():
1064-
fillFunctionParameter(funcDesc, funcCont, name, value)
1066+
functionContainerSet(funcDesc, funcCont, name, value, self.bconfig)
10651067
# Add RFC call to transaction
10661068
rc = RfcInvokeInTransaction(self._tHandle, funcCont, &errorInfo)
10671069
if rc != RFC_OK:
@@ -1238,7 +1240,7 @@ cdef class Connection:
12381240
self._error(&errorInfo)
12391241
try:
12401242
for name, value in params.iteritems():
1241-
fillFunctionParameter(funcDesc, funcCont, name, value)
1243+
functionContainerSet(funcDesc, funcCont, name, value, self.bconfig)
12421244
# Add RFC call to unit
12431245
rc = RfcInvokeInUnit(self._uHandle, funcCont, &errorInfo)
12441246
if rc != RFC_OK:
@@ -1615,15 +1617,15 @@ cdef RFC_RC genericHandler(RFC_CONNECTION_HANDLE rfcHandle, RFC_FUNCTION_HANDLE
16151617

16161618
# Filter out variables that are of direction u'RFC_EXPORT'
16171619
# (these will be set by the callback function)
1618-
func_handle_variables = wrapResult(funcDesc, funcHandle, RFC_EXPORT, server.rstrip)
1620+
func_handle_variables = functionContainerGet(funcDesc, funcHandle, RFC_EXPORT, server.bconfig)
16191621

16201622
# Invoke callback function
16211623
result = callback(request_context, **func_handle_variables)
16221624

16231625
# Return results
16241626
if context["call_type"] != UnitCallType.background_unit:
16251627
for name, value in result.iteritems():
1626-
fillFunctionParameter(funcDesc, funcHandle, name, value)
1628+
functionContainerSet(funcDesc, funcHandle, name, value, server.bconfig)
16271629

16281630
# Server exception handling: cf. SAP NetWeaver RFC SDK 7.50
16291631
# 5.1 Preparing a Server Program for Receiving RFC Requests
@@ -1687,9 +1689,16 @@ cdef class Server:
16871689
16881690
:type server_params: dict
16891691
1690-
:param config: Configuration of the instance. Allowed keys are:
1692+
:param config: Configuration of server instance. Allowed keys are:
16911693
1692-
``debug``
1694+
* ``dtime``
1695+
ABAP DATE and TIME strings are returned as Python datetime date and time objects,
1696+
instead of ABAP date and time strings (default is False)
1697+
1698+
* ``rstrip``
1699+
right strips strings returned from RFC call (default is True)
1700+
1701+
* ``debug``
16931702
For testing/debugging operations. If True, the server
16941703
behaves more permissive, e.g. allows incoming calls without a
16951704
valid connection handle. (default is False)
@@ -1700,7 +1709,9 @@ cdef class Server:
17001709
thereof if the connection attempt fails.
17011710
"""
17021711
cdef public bint debug
1712+
cdef public bint dtime
17031713
cdef public bint rstrip
1714+
cdef public unsigned bconfig
17041715
cdef Connection _client_connection
17051716
cdef ConnectionParameters _server_handle_params
17061717
cdef RFC_SERVER_HANDLE _server_handle
@@ -1746,14 +1757,21 @@ cdef class Server:
17461757
return self.alive
17471758

17481759
def __cinit__(self, server_params, client_params, config=None):
1749-
# config parsing
1760+
# check and set server configuration
17501761
config = config or {}
1762+
self.dtime = config.get('dtime', False)
17511763
self.debug = config.get('debug', False)
17521764
self.rstrip = config.get('rstrip', True)
17531765
server_context["server_log"] = config.get("server_log", False)
17541766
server_context["auth_check"] = config.get("auth_check", default_auth_check)
17551767
server_context["port"] = config.get("port", 8080)
17561768

1769+
self.bconfig = 0
1770+
if self.dtime:
1771+
self.bconfig |= _MASK_DTIME
1772+
if self.rstrip:
1773+
self.bconfig |= _MASK_RSTRIP
1774+
17571775
self._server_handle_params = ConnectionParameters(**server_params)
17581776
self._client_connection = Connection(**client_params)
17591777
self._server_thread=Thread(target=self.serve)
@@ -2391,7 +2409,7 @@ cdef class Throughput:
23912409
# FILL FUNCTIONS #
23922410
################################################################################
23932411

2394-
cdef fillFunctionParameter(RFC_FUNCTION_DESC_HANDLE funcDesc, RFC_FUNCTION_HANDLE container, name, value):
2412+
cdef functionContainerSet(RFC_FUNCTION_DESC_HANDLE funcDesc, RFC_FUNCTION_HANDLE container, name, value, unsigned config):
23952413
cdef RFC_RC rc
23962414
cdef RFC_ERROR_INFO errorInfo
23972415
cdef RFC_PARAMETER_DESC paramDesc
@@ -2400,9 +2418,9 @@ cdef fillFunctionParameter(RFC_FUNCTION_DESC_HANDLE funcDesc, RFC_FUNCTION_HANDL
24002418
free(cName)
24012419
if rc != RFC_OK:
24022420
raise wrapError(&errorInfo)
2403-
fillVariable(paramDesc.type, container, paramDesc.name, value, paramDesc.typeDescHandle)
2421+
fillVariable(paramDesc.type, container, paramDesc.name, value, paramDesc.typeDescHandle, config)
24042422

2405-
cdef fillStructureField(RFC_TYPE_DESC_HANDLE typeDesc, RFC_STRUCTURE_HANDLE container, name, value):
2423+
cdef fillStructureField(RFC_TYPE_DESC_HANDLE typeDesc, RFC_STRUCTURE_HANDLE container, name, value, unsigned config):
24062424
cdef RFC_RC rc
24072425
cdef RFC_ERROR_INFO errorInfo
24082426
cdef RFC_FIELD_DESC fieldDesc
@@ -2411,9 +2429,9 @@ cdef fillStructureField(RFC_TYPE_DESC_HANDLE typeDesc, RFC_STRUCTURE_HANDLE cont
24112429
free(cName)
24122430
if rc != RFC_OK:
24132431
raise wrapError(&errorInfo)
2414-
fillVariable(fieldDesc.type, container, fieldDesc.name, value, fieldDesc.typeDescHandle)
2432+
fillVariable(fieldDesc.type, container, fieldDesc.name, value, fieldDesc.typeDescHandle, config)
24152433

2416-
cdef fillTable(RFC_TYPE_DESC_HANDLE typeDesc, RFC_TABLE_HANDLE container, lines):
2434+
cdef fillTable(RFC_TYPE_DESC_HANDLE typeDesc, RFC_TABLE_HANDLE container, lines, unsigned config):
24172435
cdef RFC_ERROR_INFO errorInfo
24182436
cdef RFC_STRUCTURE_HANDLE lineHandle
24192437
cdef unsigned int rowCount = int(len(lines))
@@ -2425,12 +2443,12 @@ cdef fillTable(RFC_TYPE_DESC_HANDLE typeDesc, RFC_TABLE_HANDLE container, lines)
24252443
line = lines[i]
24262444
if type(line) is dict:
24272445
for name, value in line.iteritems():
2428-
fillStructureField(typeDesc, lineHandle, name, value)
2446+
fillStructureField(typeDesc, lineHandle, name, value, config)
24292447
else:
2430-
fillStructureField(typeDesc, lineHandle, '', line)
2448+
fillStructureField(typeDesc, lineHandle, '', line, config)
24312449
i += 1
24322450

2433-
cdef fillVariable(RFCTYPE typ, RFC_FUNCTION_HANDLE container, SAP_UC* cName, value, RFC_TYPE_DESC_HANDLE typeDesc):
2451+
cdef fillVariable(RFCTYPE typ, RFC_FUNCTION_HANDLE container, SAP_UC* cName, value, RFC_TYPE_DESC_HANDLE typeDesc, unsigned config):
24342452
cdef RFC_RC rc
24352453
cdef RFC_ERROR_INFO errorInfo
24362454
cdef RFC_STRUCTURE_HANDLE struct
@@ -2447,14 +2465,14 @@ cdef fillVariable(RFCTYPE typ, RFC_FUNCTION_HANDLE container, SAP_UC* cName, val
24472465
if rc != RFC_OK:
24482466
raise wrapError(&errorInfo)
24492467
for name, value in value.iteritems():
2450-
fillStructureField(typeDesc, struct, name, value)
2468+
fillStructureField(typeDesc, struct, name, value, config)
24512469
elif typ == RFCTYPE_TABLE:
24522470
if type(value) is not list:
24532471
raise TypeError('list required for table parameter, received', str(type(value)))
24542472
rc = RfcGetTable(container, cName, &table, &errorInfo)
24552473
if rc != RFC_OK:
24562474
raise wrapError(&errorInfo)
2457-
fillTable(typeDesc, table, value)
2475+
fillTable(typeDesc, table, value, config)
24582476
elif typ == RFCTYPE_BYTE:
24592477
bValue = fillBytes(value)
24602478
rc = RfcSetBytes(container, cName, bValue, int(len(value)), &errorInfo)
@@ -2549,8 +2567,10 @@ cdef fillVariable(RFCTYPE typ, RFC_FUNCTION_HANDLE container, SAP_UC* cName, val
25492567
if len(value) != 6:
25502568
format_ok = False
25512569
else:
2552-
if len(value.rstrip()) > 0:
2553-
time(int(value[:2]), int(value[2:4]), int(value[4:6]))
2570+
# plausability check if Python datetime format used
2571+
if config & _MASK_DTIME:
2572+
if len(value.rstrip()) > 0:
2573+
time(int(value[:2]), int(value[2:4]), int(value[4:6]))
25542574
cValue = fillString(value)
25552575
except Exception as ex:
25562576
format_ok = False
@@ -2773,11 +2793,11 @@ cdef wrapFunctionDescription(RFC_FUNCTION_DESC_HANDLE funcDesc):
27732793
return func_desc
27742794

27752795

2776-
cdef wrapResult(
2796+
cdef functionContainerGet(
27772797
RFC_FUNCTION_DESC_HANDLE funcDesc,
27782798
RFC_FUNCTION_HANDLE container,
27792799
RFC_DIRECTION filter_parameter_direction,
2780-
config
2800+
unsigned config
27812801
):
27822802
"""
27832803
:param funcDesc: a C pointer to a function description.
@@ -2826,7 +2846,7 @@ cdef wrapUnitAttributes(RFC_UNIT_ATTRIBUTES *uattr):
28262846
unit_attributes['sending_time'] = wrapString(uattr.sendingTime, 6, True)
28272847
return unit_attributes
28282848

2829-
cdef wrapStructure(RFC_TYPE_DESC_HANDLE typeDesc, RFC_STRUCTURE_HANDLE container, config):
2849+
cdef wrapStructure(RFC_TYPE_DESC_HANDLE typeDesc, RFC_STRUCTURE_HANDLE container, unsigned config):
28302850
cdef unsigned i, fieldCount
28312851
cdef RFC_FIELD_DESC fieldDesc
28322852
RfcGetFieldCount(typeDesc, &fieldCount, NULL)
@@ -2857,7 +2877,7 @@ cdef wrapStructure(RFC_TYPE_DESC_HANDLE typeDesc, RFC_STRUCTURE_HANDLE container
28572877
# RfcMoveTo(self.container, i, &errorInfo)
28582878
# return wrapStructure(self.typeDesc, self.container)
28592879

2860-
cdef wrapTable(RFC_TYPE_DESC_HANDLE typeDesc, RFC_TABLE_HANDLE container, config):
2880+
cdef wrapTable(RFC_TYPE_DESC_HANDLE typeDesc, RFC_TABLE_HANDLE container, unsigned config):
28612881
cdef RFC_ERROR_INFO errorInfo
28622882
cdef unsigned rowCount
28632883
# # For debugging in tables (cf. class TableCursor)
@@ -2880,7 +2900,7 @@ cdef wrapVariable(
28802900
SAP_UC* cName,
28812901
unsigned cLen,
28822902
RFC_TYPE_DESC_HANDLE typeDesc,
2883-
config
2903+
unsigned config
28842904
):
28852905
cdef RFC_RC rc
28862906
cdef RFC_ERROR_INFO errorInfo

tests/test_datatypes.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from locale import LC_ALL, localeconv, setlocale
88

99
import pytest
10-
1110
from pyrfc import Connection, ExternalRuntimeError, set_locale_radix
11+
1212
from tests.abap_system import connection_info
1313
from tests.config import (
1414
BYTEARRAY_TEST,
@@ -234,30 +234,30 @@ def test_bcd_floats_accept_floats():
234234
IS_INPUT=IS_INPUT,
235235
IV_COUNT=0,
236236
)["ES_OUTPUT"]
237-
assert type(output["ZFLTP"]) is float
237+
assert isinstance(output["ZFLTP"], float)
238238
assert IS_INPUT["ZFLTP"] == output["ZFLTP"]
239239

240-
assert type(output["ZDEC"]) is Decimal
240+
assert isinstance(output["ZDEC"], Decimal)
241241
assert str(IS_INPUT["ZDEC"]) == str(output["ZDEC"])
242242
assert IS_INPUT["ZDEC"] == float(output["ZDEC"])
243243

244-
assert type(output["ZDECF16_MIN"]) is Decimal
244+
assert isinstance(output["ZDECF16_MIN"], Decimal)
245245
assert str(IS_INPUT["ZDECF16_MIN"]) == str(output["ZDECF16_MIN"])
246246
assert IS_INPUT["ZDECF16_MIN"] == float(output["ZDECF16_MIN"])
247247

248-
assert type(output["ZDECF34_MIN"]) is Decimal
248+
assert isinstance(output["ZDECF34_MIN"], Decimal)
249249
assert str(IS_INPUT["ZDECF34_MIN"]) == str(output["ZDECF34_MIN"])
250250
assert IS_INPUT["ZDECF34_MIN"] == float(output["ZDECF34_MIN"])
251251

252-
assert type(output["ZCURR"]) is Decimal
252+
assert isinstance(output["ZCURR"], Decimal)
253253
assert str(IS_INPUT["ZCURR"]) == str(output["ZCURR"])
254254
assert IS_INPUT["ZCURR"] == float(output["ZCURR"])
255255

256-
assert type(output["ZQUAN"]) is Decimal
256+
assert isinstance(output["ZQUAN"], Decimal)
257257
assert str(IS_INPUT["ZQUAN"]) == str(output["ZQUAN"])
258258
assert IS_INPUT["ZQUAN"] == float(output["ZQUAN"])
259259

260-
assert type(output["ZQUAN_SIGN"]) is Decimal
260+
assert isinstance(output["ZQUAN_SIGN"], Decimal)
261261
assert str(IS_INPUT["ZQUAN_SIGN"]) == str(output["ZQUAN_SIGN"])
262262
assert IS_INPUT["ZQUAN_SIGN"] == float(output["ZQUAN_SIGN"])
263263

@@ -419,8 +419,8 @@ def test_raw_types_accept_bytearray():
419419
)["ES_OUTPUT"]
420420
assert output["ZRAW"] == ZRAW + DIFF
421421
assert output["ZRAWSTRING"] == ZRAW
422-
assert type(output["ZRAW"]) is bytes
423-
assert type(output["ZRAWSTRING"]) is bytes
422+
assert isinstance(output["ZRAW"], bytes)
423+
assert isinstance(output["ZRAWSTRING"], bytes)
424424

425425

426426
def test_date_time():
@@ -445,7 +445,8 @@ def test_date_time():
445445
{"RFCDATE": "20161231", "RFCTIME": 123456}, # wrong time type
446446
]
447447
for index, dt in enumerate(DATETIME_TEST):
448-
if index < 6:
448+
print(index, dt)
449+
if index < 6 or index == 16:
449450
res = client.call("STFC_STRUCTURE", IMPORTSTRUCT=dt)["ECHOSTRUCT"]
450451
assert dt["RFCDATE"] == res["RFCDATE"]
451452
if dt["RFCTIME"] == "":

0 commit comments

Comments
 (0)