-
Notifications
You must be signed in to change notification settings - Fork 144
/
_cyrfc.pyx
executable file
·3372 lines (2929 loc) · 139 KB
/
_cyrfc.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# SPDX-FileCopyrightText: 2013 SAP SE Srdjan Boskovic <[email protected]>
#
# SPDX-License-Identifier: Apache-2.0
"""The _pyrfc C-extension module"""
from libc.stdint cimport uintptr_t
from libc.stdlib cimport free, malloc
from socket import gethostname
from collections.abc import Iterable
from datetime import date, datetime, time
from decimal import Decimal
from enum import Enum, auto
from locale import localeconv
from os.path import isfile, join
from sys import exc_info, platform, version_info
from threading import Thread, Timer
from pyrfc.csapnwrfc cimport *
from pyrfc._exception import *
from pyrfc._utils import enum_names, enum_values
################################################################################
# Configuration options
################################################################################
# configuration bitmasks, internal use
_MASK_DTIME = 0x01
_MASK_RETURN_IMPORT_PARAMS = 0x02
_MASK_RSTRIP = 0x04
_MASK_CHECK_DATE = 0x08
_MASK_CHECK_TIME = 0x10
_LOCALE_RADIX = localeconv()["decimal_point"]
################################################################################
# Enumerators, external and internal use
################################################################################
# RFC parameter direction
class RfcParameterDirection(Enum):
RFC_IMPORT = RFC_DIRECTION.RFC_IMPORT
RFC_EXPORT = RFC_DIRECTION.RFC_EXPORT
RFC_CHANGING = RFC_DIRECTION.RFC_CHANGING
RFC_TABLES = RFC_DIRECTION.RFC_TABLES
# RFC field type
class RfcFieldType(Enum):
RFCTYPE_CHAR = RFCTYPE.RFCTYPE_CHAR
RFCTYPE_DATE = RFCTYPE.RFCTYPE_DATE
RFCTYPE_BCD = RFCTYPE.RFCTYPE_BCD
RFCTYPE_TIME = RFCTYPE.RFCTYPE_TIME
RFCTYPE_BYTE = RFCTYPE.RFCTYPE_BYTE
RFCTYPE_TABLE = RFCTYPE.RFCTYPE_TABLE
RFCTYPE_NUM = RFCTYPE.RFCTYPE_NUM
RFCTYPE_FLOAT = RFCTYPE.RFCTYPE_FLOAT
RFCTYPE_INT = RFCTYPE.RFCTYPE_INT
RFCTYPE_INT2 = RFCTYPE.RFCTYPE_INT2
RFCTYPE_INT1 = RFCTYPE.RFCTYPE_INT1
RFCTYPE_NULL = RFCTYPE.RFCTYPE_NULL
RFCTYPE_ABAPOBJECT = RFCTYPE.RFCTYPE_ABAPOBJECT
RFCTYPE_STRUCTURE = RFCTYPE.RFCTYPE_STRUCTURE
RFCTYPE_DECF16 = RFCTYPE.RFCTYPE_DECF16
RFCTYPE_DECF34 = RFCTYPE.RFCTYPE_DECF34
RFCTYPE_XMLDATA = RFCTYPE.RFCTYPE_XMLDATA
RFCTYPE_STRING = RFCTYPE.RFCTYPE_STRING
RFCTYPE_XSTRING = RFCTYPE.RFCTYPE_XSTRING
RFCTYPE_INT8 = RFCTYPE.RFCTYPE_INT8
RFCTYPE_UTCLONG = RFCTYPE.RFCTYPE_UTCLONG
RFCTYPE_UTCSECOND = RFCTYPE.RFCTYPE_UTCSECOND
RFCTYPE_UTCMINUTE = RFCTYPE.RFCTYPE_UTCMINUTE
RFCTYPE_DTDAY = RFCTYPE.RFCTYPE_DTDAY
RFCTYPE_DTWEEK = RFCTYPE.RFCTYPE_DTWEEK
RFCTYPE_DTMONTH = RFCTYPE.RFCTYPE_DTMONTH
RFCTYPE_TSECOND = RFCTYPE.RFCTYPE_TSECOND
RFCTYPE_TMINUTE = RFCTYPE.RFCTYPE_TMINUTE
RFCTYPE_CDAY = RFCTYPE.RFCTYPE_CDAY
# bgRFC unit state
class UnitState(Enum):
not_found = RFC_UNIT_STATE.RFC_UNIT_NOT_FOUND
in_process = RFC_UNIT_STATE.RFC_UNIT_IN_PROCESS
committed = RFC_UNIT_STATE.RFC_UNIT_COMMITTED
rolled_back = RFC_UNIT_STATE.RFC_UNIT_ROLLED_BACK
confirmed = RFC_UNIT_STATE.RFC_UNIT_CONFIRMED
created = auto()
executed = auto()
# bgRFC status
class RCStatus(Enum):
OK = RFC_RC.RFC_OK
RFC_NOT_FOUND = RFC_RC.RFC_NOT_FOUND
RFC_EXTERNAL_FAILURE = RFC_RC.RFC_EXTERNAL_FAILURE
RFC_EXECUTED = RFC_RC.RFC_EXECUTED
# bgRFCunit call type
class UnitCallType(Enum):
synchronous = RFC_CALL_TYPE.RFC_SYNCHRONOUS
transactional = RFC_CALL_TYPE.RFC_TRANSACTIONAL
queued = RFC_CALL_TYPE.RFC_QUEUED
background_unit = RFC_CALL_TYPE.RFC_BACKGROUND_UNIT
################################################################################
# NW RFC SDK FUNCTIONALITY
################################################################################
def get_nwrfclib_version():
"""Get SAP NW RFC Lib version
:returns: tuple of major, minor and patch level and OS platform
"""
cdef unsigned major = 0
cdef unsigned minor = 0
cdef unsigned patchlevel = 0
RfcGetVersion(&major, &minor, &patchlevel)
return {'major': major, 'minor': minor, 'patchLevel': patchlevel, 'platform': platform}
def set_ini_file_directory(path_name):
"""Sets the directory in which to search for the sapnwrfc.ini file
:param path_name: Directory in which to search for the sapnwrfc.ini file.
:type path_name: string
:return: nothing, raises an error
"""
if type(path_name) is not str:
raise TypeError('sapnwrfc.ini path is not a string:', path_name)
cdef RFC_ERROR_INFO errorInfo
cdef SAP_UC pathName [512]
if not isfile(join(path_name, "sapnwrfc.ini")):
raise TypeError('sapnwrfc.ini not found in:', path_name)
pathName = fillString(path_name)
cdef RFC_RC rc = RfcSetIniPath(pathName, &errorInfo)
if rc != RFC_OK:
raise wrapError(&errorInfo)
def reload_ini_file():
"""Reloads the contents of the sapnwrfc.ini file into memory.
Searches the directory given by ``RfcSetIniPath()`` (or the current working directory)
for the file sapnwrfc.ini and loads its contents into memory. Reloading the sapnwrfc.ini
file is only necessary after the file has been manually edited.
If you want to use a sapnwrfc.ini file in a different location, consider using ``RfcSetIniPath()``.
Note: If a file with the name ``sapnwrfc.ini`` does not exist in the given directory,
this is not considered an error! Default settings are used in this case.
:return: nothing, raises an error
"""
cdef RFC_ERROR_INFO errorInfo
cdef RFC_RC rc = RfcReloadIniFile (&errorInfo)
if rc != RFC_OK:
raise wrapError(&errorInfo)
def language_iso_to_sap(lang_iso):
"""Language code conversion of ISO code to SAP code.
:param lang_iso: Language ISO code
:type lang_iso: string
:return: SAP language code of char 1 type
:raises: :exc:`~pyrfc.RFCError` or a subclass
if ISO to SAP code conversion fails.
"""
cdef SAP_UC *uclang_iso = fillString(lang_iso)
cdef SAP_UC uclang_sap[8]
cdef RFC_ERROR_INFO errorInfo
cdef RFC_RC rc = RfcLanguageIsoToSap(uclang_iso, uclang_sap, &errorInfo)
free(uclang_iso)
if rc != RFC_OK:
raise wrapError(&errorInfo)
return wrapString(uclang_sap, 1)
def language_sap_to_iso(lang_sap):
"""Language code conversion of SAP code to ISO code.
:param lang_sap: Language SAP code
:type lang_sap: string
:return: ISO language code
:raises: :exc:`~pyrfc.RFCError` or a subclass
if SAP to ISO code conversion fails.
"""
cdef SAP_UC *uclang_sap = fillString(lang_sap)
cdef SAP_UC uclang_iso[16]
cdef RFC_ERROR_INFO errorInfo
cdef RFC_RC rc = RfcLanguageSapToIso(uclang_sap, uclang_iso, &errorInfo)
free(uclang_sap)
if rc != RFC_OK:
raise wrapError(&errorInfo)
return wrapString(uclang_iso, 2)
def set_cryptolib_path(path_name):
"""Sets the absolute path to the sapcrypto library to enable TLS encryption via Websocket Rfc.
The parameter path_name needs also to contain the name of the library.
This function has the same effect as the sapnwrfc.ini parameter TLS_SAPCRYPTOLIB.
This API cannot reset a new path to the library during runtime. Once set, the path is definitive.
:param path_name: Absolute path to crypto library
:type path_name: string
:return: nothing, raises an error
"""
if type(path_name) is not str:
raise TypeError('sapnwrfc.ini path is not a string:', path_name)
cdef RFC_ERROR_INFO errorInfo
cdef SAP_UC pathName [512]
if not isfile(path_name):
raise TypeError('Crypto library not found:', path_name)
pathName = fillString(path_name)
cdef RFC_RC rc = RfcLoadCryptoLibrary(pathName, &errorInfo)
if rc != RFC_OK:
raise wrapError(&errorInfo)
def set_locale_radix(value=None):
"""Sets the locale radix for decimal conversions.
:param value: Locale radix like ``.`` or ``,``
:type path_name: string
:return: New radix set
"""
global _LOCALE_RADIX
if value is None:
value = localeconv()['decimal_point']
_LOCALE_RADIX = value
return _LOCALE_RADIX
cdef _cancel_connection(client_connection):
cdef RFC_RC rc
cdef RFC_ERROR_INFO errorInfo
if client_connection.handle is not None:
rc = RfcCancel(<RFC_CONNECTION_HANDLE><uintptr_t>client_connection.handle, &errorInfo)
if rc != RFC_OK or errorInfo.code != RFC_OK:
raise wrapError(&errorInfo)
def cancel_connection(client_connection):
"""Immediately cancels the RFC call and closes the connection.
Can be used only on an RFC client connection call cancellation with timeout can be done automatically, without using this method explicitely.
The ``timeout`` option can be at connection level, when creating connection instance, or at RFC call level, as
RFC ``Connection.call()`` option. Either way, the connection will be cancelled if RFC call takes longer than ``timeout`` seconds.
:param client_connection: RFC client connection instance to be cancelled
:type client_connection: Connection
:raises: :exc:`~pyrfc.RFCError` or a subclass
thereof if the connection cannot be cancelled cleanly.
"""
Thread(target=_cancel_connection, args=(client_connection,)).start()
################################################################################
# CONNECTION PARAMETERS
################################################################################
cdef class ConnectionParameters:
"""Connection parameters instance in SAP unicode format
:param args: Connection parameters like ASHOST="ABC" etc
:type args: positional
:returns: Nothing
"""
cdef unsigned _params_count
cdef RFC_CONNECTION_PARAMETER *_params
def __cinit__(self, **params):
self._params_count = <unsigned> len(params)
if self._params_count < 1:
raise RFCError("Connection parameters missing")
self._params = <RFC_CONNECTION_PARAMETER*> malloc(self._params_count * sizeof(RFC_CONNECTION_PARAMETER))
cdef int i = 0
for name, value in params.iteritems():
self._params[i].name = fillString(name)
if type(value) is not str:
raise RFCError(f"Connection parameter '{name}' value is not string")
self._params[i].value = fillString(value)
i += 1
def __dealloc__(self):
self._free()
def _free(self):
if self._params_count > 0:
for i in range(self._params_count):
free(<SAP_UC*>self._params[i].name)
free(<SAP_UC*> self._params[i].value)
free(self._params)
self._params_count = 0
def free(self):
self._free()
################################################################################
# Type Description
################################################################################
class TypeDescription(object):
"""A type description
This class wraps the RFC_TYPE_DESC_HANDLE as e.g. contained in
a parameter description of a function description.
:param name: Name of the type.
:param nuc_length: Length of the type in non unicode systems.
:param uc_length: Length of the type in unicode systems.
*Attributes and methods*
**name**
The name of the function.
**nuc_length**
The length in bytes if chars are non unicode.
**uc_length**
The length in bytes if chars are unicode.
**fields**
The fields as a list of dicts.
"""
def __init__(self, name, nuc_length, uc_length):
self.fields = []
if len(name)<1 or len(name)>30:
raise TypeError(f"field 'name' (string) '{name}' should be from 1-30 chars.")
for int_field in [nuc_length, uc_length]:
if type(int_field) not in [int, long]:
raise TypeError(f"field '{name}' length '{int_field}' must be of type integer")
self.name = name
self.nuc_length = nuc_length
self.uc_length = uc_length
def add_field(self, name, field_type, nuc_length, uc_length, nuc_offset,
uc_offset, decimals=0, type_description=None):
"""Adds a field to the type description.
:param name: Field name
:type name: string (30)
:param field_type: RfcFieldType enum name
:type field_type: string
:param nuc_length: NUC length
:type nuc_length: int
:param uc_length: UC length
:type uc_length: int
:param nuc_offset: NUC offset.
:type nuc_offset: int
:param uc_offset: UC offset.
:type uc_offset: int
:param decimals: Decimals (default=0)
:type decimals: int
:param type_description: An object of class TypeDescription or None (default=None)
:type type_description: object of class TypeDescription
"""
if len(name)<1:
return None
if len(name)>30:
raise TypeError(f"field 'name' (string) '{name}' should be from 1-30 chars.")
if field_type not in enum_names(RfcFieldType):
raise TypeError(f"'field_type' (string) '{field_type}' must be in {enum_names(RfcFieldType)}")
for int_field in [nuc_length, nuc_offset, uc_length, uc_offset]:
if not isinstance(int_field, (int, long)):
raise TypeError(f"field '{name}' length '{int_field}' must be of type integer")
self.fields.append({
'name': name,
'field_type': field_type,
'nuc_length': nuc_length,
'nuc_offset': nuc_offset,
'uc_length': uc_length,
'uc_offset': uc_offset,
'decimals': decimals,
'type_description': type_description
})
def __repr__(self):
return f"<TypeDescription '{self.name}' with {len(self.fields)} " \
f"fields (n/uclength={self.nuc_length}/{self.uc_length})>"
################################################################################
# Function Description
################################################################################
class FunctionDescription(object):
"""A function description
This class wraps the RFC_FUNCTION_DESC_HANDLE as e.g. returned by
RfcGetFunctionDesc() and used for server functionality.
.. WARNING::
Actually, the function description does not support exceptions
(cf. RfcAddException() etc.)
:param name: Name of the function.
*Attributes and methods*
**name**
The name of the function.
**parameters**
The parameters as a list of dicts.
"""
def __init__(self, name):
self.name = name
self.parameters = []
def add_parameter(self, name, parameter_type, direction, nuc_length,
uc_length, decimals=0, default_value="", parameter_text="",
optional=False, type_description=None):
"""Adds a parameter to the function description.
:param name: Parameter name
:type name: string (30)
:param parameter_type: RfcFieldType enum name
:type parameter_type: string
:param direction: RfcParameterDirection enum name
:type direction: string
:param nuc_length: NUC length
:type nuc_length: int
:param uc_length: UC length
:type uc_length: int
:param decimals: Decimals (default=0)
:type decimals: int
:param default_value: Default value (default="")
:type default_value: string (30)
:param parameter_text: Parameter text (default="")
:type parameter_text: string (79)
:param optional: Is the parameter optional (default=False)
:type optional: bool
:param type_description: An object of class TypeDescription or None (default=None)
:type type_description: object of class TypeDescription
"""
if len(name)<1 or len(name)>30:
raise TypeError(f"field 'name' (string) {name} should be from 1-30 chars.")
if parameter_type not in enum_names(RfcFieldType):
raise TypeError(f"'parameter_type' (string) '{parameter_type}' must be in {enum_names(RfcFieldType)}")
if direction not in enum_names(RfcParameterDirection):
raise TypeError(f"'direction' (string) '{direction}' must be in '{enum_names(RfcParameterDirection)}'")
if len(default_value)>30:
raise TypeError(f"'default_value' (string) '{default_value}' must not exceed 30 chars.")
if len(parameter_text)>79:
raise TypeError("'parameter_text' (string) '{parameter_text}' must not exceed 79 chars.")
self.parameters.append({
'name': name,
'parameter_type': parameter_type,
'direction': direction,
'nuc_length': nuc_length,
'uc_length': uc_length,
'decimals': decimals,
'default_value': default_value,
'parameter_text': parameter_text,
'optional': optional,
'type_description': type_description
})
def __repr__(self):
return f"<FunctionDescription '{self.name}' with {len(self.parameters)} params>"
# NOTES ON ERROR HANDLING
# If an error occurs within a connection object, the error may - depending
# on the error code - affect the status of the connection object.
# Therefore, the _error() method is called instead of raising the error
# directly.
# However, updating the connection status is not possible in the
# fill/wrap-functions, as there is no connection object available. But this
# should not be a problem as we do not expect connection-affecting errors if
# no connection is present.
#
# NOTES ON NOGIL:
# NW RFC Lib function call may take a while (e.g. invoking RFC),
# other threads may be blocked meanwhile. To avoid this, some statements
# calling NW RFC Lib functions are executed within a "with nogil:" block,
# thereby releasing the Python global interpreter lock (GIL).
################################################################################
# CLIENT CONNECTION
################################################################################
cdef class Connection:
"""A connection to an SAP backend system
Instantiating an :class:`pyrfc.Connection` object will
automatically attempt to open a connection the SAP backend.
:param config: Configuration of the client connection, valid for all RFC calls of given connection. Allowed keys are:
* ``dtime``
ABAP DATE and TIME strings are returned as Python datetime date and time objects,
instead of ABAP date and time strings (default is False)
* ``check_date``
Check the date value before writing to function container (default is True).
When deactivated, the validation shall be done by SAP NW RFC SDK and
ABAP application.
* ``check_time``
Check the time value before writing to function container (default is True).
When deactivated, the validation shall be done by SAP NW RFC SDK and
ABAP application.
* ``rstrip``
right strips strings returned from RFC call (default is True)
* ``return_import_params``
importing parameters are returned by the RFC call (default is False)
* ``timeout``
Cancel connection if ongoing RFC calls takes longer than ``timeout`` seconds.
Timeout can be also set as option for particular RFC call, overriding timeout set at connection level.
Examples: https://github.com/SAP/PyRFC/tree/main/examples/timeout
:type config: dict or None (default)
:param params: SAP connection parameters. The parameters consist of
``client``, ``user``, ``passwd``, ``lang``, ``trace``
and additionally one of
* Direct application server logon: ``ashost``, ``sysnr``.
* Logon with load balancing: ``mshost``, ``msserv``, ``sysid``,
``group``.
``msserv`` is needed only, if the service of the message server
is not defined as sapms<SYSID> in /etc/services.
* When logging on with SNC, ``user`` and ``passwd`` are to be replaced by
``snc_qop``, ``snc_myname``, ``snc_partnername``, and optionally
``snc_lib``.
(If ``snc_lib`` is not specified, the RFC library uses the "global" GSS library
defined via environment variable SNC_LIB.)
:type params: Keyword parameters
:raises: :exc:`~pyrfc.RFCError` or a subclass
thereof if the connection attempt fails.
"""
cdef unsigned bconfig
cdef public dict __config
cdef bint active_transaction
cdef bint active_unit
cdef RFC_CONNECTION_HANDLE _handle
cdef RFC_TRANSACTION_HANDLE _tHandle
cdef RFC_UNIT_HANDLE _uHandle
cdef ConnectionParameters _connection
@property
def version(self):
"""Get SAP NW RFC SDK and PyRFC binding versions
:returns: SAP NW RFC SDK major, minor, patch level and PyRFC binding version
"""
cdef unsigned major = 0
cdef unsigned minor = 0
cdef unsigned patchlevel = 0
RfcGetVersion(&major, &minor, &patchlevel)
return {'major': major, 'minor': minor, 'patchLevel': patchlevel, 'platform': platform}
@property
def options(self):
"""Client connection configuration
:getter: Client connection options
:setter: Set when new connection object created
:type: dict
"""
return self.__config
@property
def handle(self):
"""Get client connection handle
:getter: Client connection handle
:type: uintptr_t
"""
return <uintptr_t>self._handle if self._handle is not NULL else None
@property
def alive(self):
"""Conection alive property
:getter: True when alive
:type: boolean
"""
return self._handle != NULL
def __init__(self, config=None, **params):
# check and set connection configuration
config = config or {}
for k in config:
if k not in['dtime', 'return_import_params', 'rstrip', 'check_date', 'check_time', 'timeout']:
raise RFCError(f"Connection configuration option '{k}' is not supported")
self.__config = {}
self.__config['rstrip'] = config.get('rstrip', True)
self.__config['return_import_params'] = config.get('return_import_params', False)
self.__config['dtime'] = config.get('dtime', False)
self.__config['check_date'] = config.get('check_date', True)
self.__config['check_time'] = config.get('check_time', True)
self.__config['timeout'] = config.get('timeout', None)
# set internal configuration
self.bconfig = 0
if self.__config['dtime']:
self.bconfig |= _MASK_DTIME
if self.__config['return_import_params']:
self.bconfig |= _MASK_RETURN_IMPORT_PARAMS
if self.__config['rstrip']:
self.bconfig |= _MASK_RSTRIP
if self.__config['check_date']:
self.bconfig |= _MASK_CHECK_DATE
if self.__config['check_time']:
self.bconfig |= _MASK_CHECK_TIME
self._connection = ConnectionParameters(**params)
self._handle = NULL
self.active_transaction = False
self.active_unit = False
self._open()
def free(self):
"""Explicitly free connection parameters and close the connection.
Note that this is usually required because the object destruction
can be delayed by the garbage collection and problems may occur
when too many connections are opened.
"""
self._close()
if self._connection is not None:
self._connection._free()
def __dealloc__(self):
self.free()
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
# Although the _close() method is also called in the destructor, the
# explicit call assures the immediate closing to the connection.
self._close()
def open(self):
"""Open client the connection
:raises: :exc:`~pyrfc.RFCError` or a subclass
thereof if the connection cannot be opened.
"""
self._open()
def reopen(self):
"""Re-open client the connection
:raises: :exc:`~pyrfc.RFCError` or a subclass
thereof if the connection cannot be re-opened.
"""
self._reopen()
def close(self):
"""Close the connection
:raises: :exc:`~pyrfc.RFCError` or a subclass
thereof if the connection cannot be closed cleanly.
"""
self._close()
def cancel(self):
"""Cancels the ongoing RFC call using `~pyrfc.cancel_connection()` function
:raises: :exc:`~pyrfc.RFCError` or a subclass
thereof if the connection cannot be cancelled cleanly.
"""
cancel_connection(self)
def __bool__(self):
return self.alive
cdef _reopen(self):
self._close()
self._open()
cdef _open(self):
cdef RFC_ERROR_INFO errorInfo
with nogil:
self._handle = RfcOpenConnection(self._connection._params, self._connection._params_count, &errorInfo)
if not self._handle:
self._error(&errorInfo)
def _close(self):
cdef RFC_ERROR_INFO errorInfo
if self._handle != NULL:
RfcCloseConnection(self._handle, &errorInfo)
# no error code check, assume closed
self._handle = NULL
cdef _error(self, RFC_ERROR_INFO* errorInfo):
"""
Error treatment of a connection.
:param errorInfo: the errorInfo data given in a RFC that returned an RC > 0.
:return: nothing, raises an error
"""
# Set alive=false if the error is in a certain group
# Before, the alive=false setting depended on the error code. However, the group seems more robust here.
# errorInfo.code in
# RFC_COMMUNICATION_FAILURE, RFC_ABAP_MESSAGE, RFC_ABAP_RUNTIME_FAILURE,
# RFC_INVALID_HANDLE, RFC_NOT_FOUND, RFC_INVALID_PARAMETER:
# if errorInfo.group in (ABAP_RUNTIME_FAILURE, LOGON_FAILURE, COMMUNICATION_FAILURE, EXTERNAL_RUNTIME_FAILURE):
# self.alive = False
raise wrapError(errorInfo)
def ping(self):
"""Send a RFC Ping through the current connection
Returns nothing.
:raises: :exc:`~pyrfc.RFCError` or a subclass
thereof if the RFC Ping fails.
"""
cdef RFC_RC rc
cdef RFC_ERROR_INFO errorInfo
rc = RfcPing(self._handle, &errorInfo)
if rc != RFC_OK:
self._error(&errorInfo)
def reset_server_context(self):
"""
Resets the SAP server context ("user context / ABAP session context")
associated with the given client connection, but does not close the connection
:raises: :exc:`~pyrfc.RFCError` or a subclass
thereof in case resetting the server context fails.
(Better close the connection in that case.).
:exc:`sapnwrf2.CommunicationError` if no conversion
was found for the
"""
cdef RFC_RC rc
cdef RFC_ERROR_INFO errorInfo
rc = RfcResetServerContext(self._handle, &errorInfo)
if rc != RFC_OK:
self._error(&errorInfo)
def get_connection_attributes(self):
"""Get connection details
:returns: Mapping of connection information keys:
* active_unit: True if there is a filled and submitted unit w/o being confirmed or destroyed.
* dest: RFC destination
* host: Own host name
* partnerHost: Partner host name
* sysNumber: R/3 system number
* sysId: R/3 system ID
* client: Client ("Mandant")
* user: User
* language: Language
* trace: Trace level (0-3)
* isoLanguage: 2-byte ISO-Language
* codepage: Own code page
* partnerCodepage: Partner code page
* rfcRole: C/S: RFC Client / RFC Server
* type: 2/3/E/R: R/2,R/3,Ext,Reg.Ext
* partnerType: 2/3/E/R: R/2,R/3,Ext,Reg.Ext
* rel: My system release
* partnerRe: Partner system release
* kernelRel: Partner kernel release
* cpicConvId: CPI-C Conversation ID
* progName: Name calling APAB program (report, module pool)
* partnerBytesPerChar: Bytes per char in backend codepage.
* partnerSystemCodepage: Partner system code page
* reserved: Reserved for later use
Note: all values, except ``active_unit`` are right stripped string values.
:raises: :exc:`~pyrfc.RFCError` or a subclass thereof if the RFC call fails.
"""
cdef RFC_RC rc
cdef RFC_ERROR_INFO errorInfo
cdef RFC_ATTRIBUTES attributes
result = {}
if self.is_valid():
rc = RfcGetConnectionAttributes(self._handle, &attributes, &errorInfo)
if rc != RFC_OK:
self._error(&errorInfo)
result = wrapConnectionAttributes(attributes)
result.update({
'active_unit': self.active_unit or self.active_transaction
})
return result
def is_valid(self):
"""
Checks an RFC connection. Can be used to check whether a client/server connection
has already been closed, or whether the NW RFC library still "considers" the connection
to be open.
.. note::
This does not guarantee that the connection is indeed still alive:
A firewall may silently have closed the connection without notifying
the endpoints. If you want to find out, whether the connection is still alive,
you'll have to use the more expensive RfcPing().
:returns: boolean
"""
cdef RFC_ERROR_INFO errorInfo
cdef RFC_INT isValid
rc = RfcIsConnectionHandleValid(self._handle, &isValid, &errorInfo)
if rc != RFC_OK or errorInfo.code != RFC_OK:
return False
return True
# def c_handle_test(self, p_handle):
# print("p:handle", p_handle)
# cdef RFC_CONNECTION_HANDLE c_handle = <RFC_CONNECTION_HANDLE><uintptr_t>p_handle
# p_handle2 = <uintptr_t>c_handle
# print("p:handle <uintptr_t>", p_handle2)
# print("c:handle", "ok" if c_handle - self._handle == 0 else "error")
def get_function_description(self, func_name):
"""Returns a function description of a function module.
:param func_name: Name of the function module whose description
will be returned.
:type func_name: string
:return: A :class:`FunctionDescription` object.
"""
cdef RFC_ERROR_INFO errorInfo
funcName = fillString(func_name.upper())
cdef RFC_FUNCTION_DESC_HANDLE funcDesc = RfcGetFunctionDesc(self._handle, funcName, &errorInfo)
free(funcName)
if not funcDesc:
self._error(&errorInfo)
return wrapFunctionDescription(funcDesc)
def call(self, func_name, options=None, **params):
"""
Invokes a remote-enabled function module via RFC.
:param func_name: Name of the function module that will be invoked.
:type func_name: string
:param options: Call options for single remote ABAP function call. Allowed keys:
- ``not_requested`` Allows to deactivate certain parameters in the function module interface.
This is particularly useful for BAPIs which have many large tables, the Python client is not interested in.
Deactivate those, to reduce network traffic and memory consumption in your application considerably.
This functionality can be used for input and output parameters. If the parameter is an input, no data for
that parameter will be sent to the backend. If it's an output, the backend will be informed not to return
data for that parameter.
- ``timeout`` Cancel RFC connection if ongoing RFC call not completed within ``timeout`` seconds.
Timeout can be also set as client connection configuration option, in which case is valid for all RFC calls.
Examples: https://github.com/SAP/PyRFC/tree/main/examples/timeout
:type options: dictionary
:param **params:
Parameter of the function module.
All non optional IMPORT, CHANGING, and TABLE parameters must be provided.
:return: Dictionary with all EXPORT, CHANGING, and TABLE parameters.
The IMPORT parameters are also given, if :attr:`Connection.config.return_import_params`
is set to ``True``.
:raises: :exc:`~pyrfc.RFCError` or a subclass thereof if the RFC call fails.
"""
cdef RFC_RC rc
cdef RFC_ERROR_INFO errorInfo
cdef RFC_ERROR_INFO openErrorInfo
cdef SAP_UC *cName
if type(func_name) is not str:
raise RFCError("Remote function module name must be unicode string, received:", func_name, type(func_name))
cdef SAP_UC *funcName = fillString(func_name)
if self._handle == NULL:
raise RFCError(f"Remote function module '{func_name}' invocation rejected because the connection is closed")
cdef RFC_FUNCTION_DESC_HANDLE funcDesc = RfcGetFunctionDesc(self._handle, funcName, &errorInfo)
free(funcName)
if not funcDesc:
self._error(&errorInfo)
cdef RFC_FUNCTION_HANDLE funcCont = RfcCreateFunction(funcDesc, &errorInfo)
if not funcCont:
self._error(&errorInfo)
cdef int isActive = 0
options = options or {}
cancel_timer = None
try: # now we have a function module
if 'not_requested' in options:
skip_parameters = options['not_requested']
if type(skip_parameters) is not list:
skip_parameters = [skip_parameters]
for name in skip_parameters:
cName = fillString(name)
rc = RfcSetParameterActive(funcCont, cName, isActive, &errorInfo)
free(cName)
if rc != RFC_OK:
self._error(&errorInfo)
# set connection timeout, starts before writing input parameters to container
timeout = options.get('timeout', self.__config['timeout'])
if timeout is not None:
cancel_timer = Timer(timeout, cancel_connection, (self,))
cancel_timer.start()
for name, value in params.iteritems():
functionContainerSet(funcDesc, funcCont, name, value, self.bconfig)
# save old handle for troubleshooting
with nogil:
rc = RfcInvoke(self._handle, funcCont, &errorInfo)
if cancel_timer is not None:
cancel_timer.cancel()
# print("invoke:", errorInfo.group, rc, self.handle, self.is_valid())
if rc != RFC_OK:
if errorInfo.code in (
RFC_COMMUNICATION_FAILURE,
RFC_ABAP_RUNTIME_FAILURE,
RFC_ABAP_MESSAGE,
RFC_EXTERNAL_FAILURE
) or errorInfo.group in (
ABAP_RUNTIME_FAILURE,
LOGON_FAILURE,
COMMUNICATION_FAILURE,
EXTERNAL_RUNTIME_FAILURE):
# Connection closed, re-open
closed_handle = self.handle
self._handle = RfcOpenConnection(self._connection._params, self._connection._params_count, &openErrorInfo)
if openErrorInfo.code != RFC_OK:
self._handle = NULL
# Communication error returned as error
errorInfo = openErrorInfo
elif errorInfo.code == RFC_CANCELED:
errorInfo.message = fillString(f"Connection was canceled: {closed_handle}. New handle: {self.handle}")
self._error(&errorInfo)
if self.bconfig & _MASK_RETURN_IMPORT_PARAMS:
return functionContainerGet(funcDesc, funcCont, <RFC_DIRECTION> 0, self.bconfig)
else:
return functionContainerGet(funcDesc, funcCont, RFC_IMPORT, self.bconfig)
finally:
if cancel_timer is not None:
cancel_timer.cancel()
RfcDestroyFunction(funcCont, NULL)
##########################################################################
# HELPER METHODS
def type_desc_get(self, type_name):
"""Removes the Type Description from SAP NW RFC Lib cache
:param type_name: system id (connection parameters sysid)
:type type_name: string
:returns: error code
"""
cdef RFC_ERROR_INFO errorInfo
typeName = fillString(type_name.upper())
cdef RFC_TYPE_DESC_HANDLE typeDesc = RfcGetTypeDesc(self._handle, typeName, &errorInfo)
free(typeName)
if typeDesc == NULL:
self._error(&errorInfo)
return wrapTypeDescription(typeDesc)
def type_desc_remove(self, sysid, type_name):
"""Removes the Type Description from SAP NW RFC Lib cache
:param sysid: system id (connection parameters sysid)
:type sysid: string
:param type_name: Name of the type to be removed
:type func_name: string
:returns: error code
"""