{"id":11252,"date":"2018-05-22T13:15:03","date_gmt":"2018-05-22T11:15:03","guid":{"rendered":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/"},"modified":"2025-10-24T09:22:44","modified_gmt":"2025-10-24T07:22:44","slug":"adding-a-documentum-extension-into-python","status":"publish","type":"post","link":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/","title":{"rendered":"Adding a Documentum extension into python"},"content":{"rendered":"<p>Many years ago, out of frustration by the poorness of scripting tools in Documentum, I realized a Documentum binding for python using the distutils and I remember how easy and straightforward it had been, even for someone not really into these things on a daily basis. Recently, I wanted to reuse that work but couldn&#8217;t find the source files, not that they were many, but I did not want to do it over again. Finally, I had to give up and admit it: I lost them for good somewhere among the tens of machines, O\/Ses, disks and USB drives I played with during that period. Those were sunny, cloudless times (got it ?). Anyway, I decided to do it again but, as I hate repeating work, by using another method of extending python: this time I went for ctypes (cf. documentation here: <a title=\"ctypes doc\" href=\"https:\/\/docs.python.org\/3\/library\/ctypes.html\" target=\"_blank\" rel=\"noopener\">https:\/\/docs.python.org\/3\/library\/ctypes.html<\/a>).<br \/>\nOne of the advantages of ctypes over distutils is that no compilation is needed, even when changing the version of python or of the O\/S because the interface <strong>is<\/strong> python, and it gets interpreted at run-time. Thanks to ctypes, there isn&#8217;t much to do and interfacing to a run-time library such as libdmcl40.so is a no brainer.<br \/>\nThere was however a big change in the evolution from python 2 to python 3: strings no longer are arrays of bytes but are now a distinct, uncompatible type storing unicode characters. Transformation functions are of course provided to go from one type to the other and back. For low-level work such as interfacing a C\/C++ shared library, the distinction is important because in C, strings are accessed as &#8220;char *&#8221;, i.e. array of bytes and one cannot just pass around python text strings with 1 to 4 bytes per character. Fortunately, there was no need to produce two versions of the interface because python 2.7, the last version of python 2, understands the type conversion functions used here:<br \/>\n<code><br \/>\nstring.encode('ascii', 'ignore') to convert python 3's strings to python 2's arrays of bytes compatible with C\/C++ char*<br \/>\nb.decode() to convert python 2's arrays of bytes to python3 unicode strings<br \/>\n<\/code><br \/>\n(see <a title=\"Unicode doc\" href=\"https:\/\/docs.python.org\/3\/howto\/unicode.html\" target=\"_blank\" rel=\"noopener\">https:\/\/docs.python.org\/3\/howto\/unicode.html<\/a>). Thus, it was sufficient to just write one version for python 3 and it would also be compatible with python 2.<br \/>\nI started my work in a venerable 32-bit Ubuntu 14.04 with python 2.7. The Documentum library I used there was the 32-bit libdmcl40.so included in the Content server v5.3 binaries. Later, I installed python 3 on the same VM and made the necessary changes to the interface so it would be accepted by this interpreter. Later on, I copied the interface file to another VM running a 64-bit Ubuntu 16.04 and v7.3 Documentum ContentServer binaries but I couldn&#8217;t make it work with the included 64-bit libdmcl40.so. I kept receiving SIGSEGV and core dumps from both python2 and python3. A case for a gdb session sometime&#8230; Fortunately, with the java-enabled libdmcl.so library, both pythons worked well, albeit with a perceptible delay at startup because of all the jars to load, a small price to pay though.<\/p>\n<h4>The interface<\/h4>\n<p>The C functions libdmcl*.so exports are the following:<br \/>\n<code><br \/>\nint dmAPIInit();<br \/>\nint dmAPIDeInit();<br \/>\nint dmAPIExec(const char *str);<br \/>\nchar* dmAPIGet(const char *str);<br \/>\nint dmAPISet(const char *str, const char *arg);<br \/>\nchar* dmAPIDesc(const char *str, int *code, int *type, int *sess);<br \/>\nchar* dmAPIEval(const char *str, const char *arg);<br \/>\nchar* dmGetPassword(const char *str);<br \/>\nint dmGetVersion( int *, int * );<br \/>\n<\/code><\/p>\n<p>However, the last 2 functions don&#8217;t seem to really be available from the library. Also, dmAPIDesc() (not to be confused with the describe server method) and dmAPIEval() are not documented in the API reference manual. Therefore, I&#8217;ve only considered the first 5 functions, the ones that really do the job as the building blocks of any Documentum script.<br \/>\nFrom within python, those functions are accessed through the wrapper functions below:<br \/>\n<code><br \/>\ndef dmInit()<br \/>\ndef dmAPIDeInit()<br \/>\ndef dmAPIGet(s)<br \/>\ndef dmAPISet(s, value)<br \/>\ndef dmAPIExec(stmt)<br \/>\n<\/code><br \/>\nThose take care of the string conversions operations so you don&#8217;t have to; they are the only ones who directly talk to the Documentum API and the only ones to use to do API stuff. Generally, they return True or a string if successful, and False or None if not.<br \/>\nEvery Documentum client should start with a call do dmInit() in order to load and initialize the libdmcl*.so library&#8217;s internal state. To guarantee that, the interface does it itself at load time. As this function is idempotent, further calls at script start up don&#8217;t have any effect. On the other hand, dmAPIDeInit() is not really necessary, just exiting the script will do.<br \/>\nHere, I named the proxy function dmInit() instead of dmAPIInit() for a reason. This function does not just invoke the library&#8217;s dmAPIInit() but also initializes the python interface and its usage of ctypes: it loads the shared library and describes the types of the API functions&#8217; arguments (argtypes) and return values (restype). Here is a snippet of its the main part:<br \/>\n<code><br \/>\ndmlib = 'libdmcl40.so'<br \/>\n...<br \/>\ndm = ctypes.cdll.LoadLibrary(dmlib); dm.restype = ctypes.c_char_p<br \/>\n...<br \/>\ndm.dmAPIInit.restype = ctypes.c_int;<br \/>\ndm.dmAPIDeInit.restype = ctypes.c_int;<br \/>\ndm.dmAPIGet.restype = ctypes.c_char_p; dm.dmAPIGet.argtypes = [ctypes.c_char_p]<br \/>\ndm.dmAPISet.restype = ctypes.c_int; dm.dmAPISet.argtypes = [ctypes.c_char_p, ctypes.c_char_p]<br \/>\ndm.dmAPIExec.restype = ctypes.c_int; dm.dmAPIExec.argtypes = [ctypes.c_char_p]<br \/>\nstatus = dm.dmAPIInit()<br \/>\n<\/code><br \/>\nThe shared library whose name is in dmlib must be in the LD_LIBRARY_PATH (or SHLIB_PATH or LIBPATH, depending on the Unix flavor); specifying its full path name does work too.\u00a0 As I wrote it before, if the script crashes, try to set it to libdmcl.so instead if it&#8217;s available.<br \/>\nThe wrapper functions are used by all the verbs documented in the API Reference Manual. When the manual says for example:<br \/>\n<code><br \/>\nFetch<br \/>\nPurpose Fetches an object from the repository without placing a lock on the object.<br \/>\nSyntax<br \/>\n<strong>dmAPIExec(\"fetch,session,object_id[,type][,persistent_cache][,consistency_check_value]\")<\/strong><br \/>\n...<br \/>\nReturn value<br \/>\nThe Fetch method returns TRUE if successful or FALSE if unsuccessful.<br \/>\n...<br \/>\n<\/code><br \/>\nit is the function dmAPIExec() that conveys the verb &#8220;fetch&#8221; and its arguments to the shared library. It takes just one argument, a string, and return, None if the call failed, a positive integer if it succeeded.<\/p>\n<p>Another example:<br \/>\n<code><br \/>\nGetservermap<br \/>\nPurpose Returns information about the servers known to a connection broker.<br \/>\nSyntax<br \/>\n<strong>dmAPIGet(\"getservermap,session,repository_name[,protocol][,host_name][,port_number]\")<\/strong><br \/>\n...<br \/>\nReturn value<br \/>\nThe Getservermap method returns a non-persistent ID for a server locator object.<br \/>\n...<br \/>\n<\/code><br \/>\nHere, it&#8217;s dmAPIGet() that does it for the verb &#8220;getservermap&#8221;. It returns an empty string if the call failed (remapped to None to be more pythonic), a non-empty one with an ID if it succeeded.<\/p>\n<p>For more usage comfort, a few functions have been added in the interface:<br \/>\n<code><br \/>\ndef connect(docbase, user_name, password):<br \/>\n\"\"\"<br \/>\nconnects to given docbase as user_name\/password;<br \/>\nreturns a session id if OK, None otherwise<br \/>\n\"\"\"<br \/>\ndef execute(session, dql_stmt):<br \/>\n\"\"\"<br \/>\nexecute non-SELECT DQL statements;<br \/>\nreturns TRUE if OK, False otherwise;<br \/>\n\"\"\"<br \/>\ndef select(session, dql_stmt, attribute_names):<br \/>\n\"\"\"<br \/>\nexecute the DQL SELECT statement passed in dql_stmt and outputs the result to stdout;<br \/>\nattributes_names is a list of attributes to extract from the result set;<br \/>\nreturn True if OK, False otherwise;<br \/>\n\"\"\"<br \/>\ndef disconnect(session):<br \/>\n\"\"\"<br \/>\ncloses the given session;<br \/>\nreturns True if no error, False otherwise;<br \/>\n\"\"\"<br \/>\n<\/code><\/p>\n<p>Basically, they only wrap some error handling code around the calls to dmAPIGet()\/dmAPIExec(). execute() and select() are just examples of how to use the interface and could be removed from it. Let&#8217;s give a look at the latter one for instance:<\/p>\n<pre class=\"brush: python; gutter: true; first-line: 1\">def select(session, dql_stmt, attribute_names):\n   \"\"\"\n   execute the DQL SELECT statement passed in dql_stmt and outputs the result to stdout;\n   attributes_names is a list of attributes to extract from the result set;\n   return True if OK, False otherwise;\n   \"\"\"\n   show(\"in select(), dql_stmt=\" + dql_stmt)\n   try:\n      query_id = dmAPIGet(\"query,\" + session + \",\" + dql_stmt)\n      if query_id is None:\n         raise(getOutOfHere)\n\n      s = \"\"\n      for attr in attribute_names:\n         s += \"[\" + attr + \"]t\"\n      print(s)\n      resp_cntr = 0\n      while dmAPIExec(\"next,\" + session + \",\" + query_id):\n         s = \"\"\n         for attr in attribute_names:\n            value = dmAPIGet(\"get,\" + session + \",\" + query_id + \",\" + attr)\n            if \"r_object_id\" == attr and value is None:\n               raise(getOutOfHere)\n            s += \"[\" + (value if value else \"None\") + \"]t\"\n         resp_cntr += 1\n         show(str(resp_cntr) + \": \" + s)\n      show(str(resp_cntr) + \" rows iterated\")\n\n      err_flag = dmAPIExec(\"close,\" + session + \",\" + query_id)\n      if not err_flag:\n         raise(getOutOfHere)\n\n      status = True\n   except getOutOfHere:\n      show(dmAPIGet(\"getmessage,\" + session).rstrip())\n      status = False\n   except Exception as e:\n      print(\"Exception in select():\")\n      print(e)\n      traceback.print_stack()\n      print(resp_cntr); print(attr); print(s); print(\"[\" + value + \"]\")\n      status = False\n   finally:\n      show(\"exiting select()\")\n      return status<\/pre>\n<p>If it weren&#8217;t for the error handling, it really looks like dmawk code fresh from the API manual !<br \/>\nAnd here are two invocations:<\/p>\n<pre class=\"brush: python; gutter: true; first-line: 1\">   print(\"\")\n   stmt = \"select r_object_id, object_name, owner_name, acl_domain, acl_name from dm_document\"\n   status = DctmAPI.select(session, stmt, (\"r_object_id\", \"object_name\", \"owner_name\", \"acl_domain\", \"acl_name\"))\n   if status:\n      print(\"select [\" + stmt + \"] was successful\")\n   else:\n      print(\"select [\" + stmt + \"] was not successful\")\n\n   print(\"\")\n   stmt = \"select count(*) from dm_document\"\n   status = DctmAPI.select(session, stmt,  [\"count(*)\"])\n   if status:\n      print(\"select [\" + stmt + \"] was successful\")\n   else:\n      print(\"select [\" + stmt + \"] was not successful\"<\/pre>\n<p>Resulting in the following output:<br \/>\n<code><br \/>\nin select(), dql_stmt=select r_object_id, object_name, owner_name, acl_domain, acl_name from dm_document<br \/>\n[r_object_id] [object_name] [owner_name] [acl_domain] [acl_name]<br \/>\n1: [0900c350800001d0] [Default Signature Page Template] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n2: [6700c35080000100] [CSEC Plugin] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n3: [6700c35080000101] [Snaplock Connector] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n4: [0900c350800001ff] [Blank Word 2007 \/ 2010 Document] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n5: [0900c35080000200] [Blank Word 2007 \/ 2010 Template] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n6: [0900c35080000201] [Blank Word 2007 \/ 2010 Macro-enabled Document] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n7: [0900c35080000202] [Blank Word 2007 \/ 2010 Macro-enabled Template] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n8: [0900c35080000203] [Blank Excel 2007 \/ 2010 Workbook] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n9: [0900c35080000204] [Blank Excel 2007 \/ 2010 Template] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n10: [0900c350800001da] [11\/21\/2017 16:31:10 dm_PostUpgradeAction] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n11: [0900c35080000205] [Blank Excel 2007 \/ 2010 Macro-enabled Workbook] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n12: [0900c35080000206] [Blank Excel 2007 \/ 2010 Macro-enabled Template] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n13: [0900c35080000207] [Blank Excel 2007 \/ 2010 Binary Workbook] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n14: [0900c35080000208] [Blank PowerPoint 2007 \/ 2010 Presentation] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n15: [0900c35080000209] [Blank PowerPoint 2007 \/ 2010 Slide Show] [dmadmin] [dmadmin] [dm_4500c35080000101]<br \/>\n...<br \/>\n880 rows iterated<br \/>\nexiting select()<br \/>\nselect [select r_object_id, object_name, owner_name, acl_domain, acl_name from dm_document] was successful<br \/>\nin select(), dql_stmt=select count(*) from dm_document<br \/>\n[count(*)]<br \/>\n1: [880]<br \/>\n1 rows iterated<br \/>\nexiting select()<br \/>\nselect [select count(*) from dm_document] was successful<\/code><\/p>\n<p>Admittedly, the above select() function could be more clever and find by itself the queried attributes by inspecting the returned collection; this is done by the variant select2dict() (see below). Also, the output could be more structured. Stay tuned on this channel, it&#8217;s coming up !<\/p>\n<h4>The packaging<\/h4>\n<p>In order to make the interface easily usable, it has been packaged into a module named DctmAPI. To use it, just add an &#8220;import DctmAPI&#8221; statement in the client script and prefix the functions from the module with &#8220;DctmAPI&#8221;, the module namespace, when calling them, as shown in the example above.<br \/>\nI&#8217;ve given some thoughts about making a class out of it but the benefits were not so obvious because many functions are so generic that most of them would have been @staticmethod of the class anyway. Moreover, the only state variable would have been the session id, so instead of carrying it around, an instance of the class would have to be used instead, no real improvement here. Even worse, as the session id would have been hidden in the instance, the statements passed to an instance object would have to be changed not to include it and leave that to the instance, which would hurt the habits of using the standard API argument format; also, as a few API verbs don&#8217;t need a session id, exceptions to the rule would need to be introduced, which would mess the class even more. Therefore, I chose to stick as closer as possible to the syntax documented in the API manual, at the only cost of introducing a namespace with the module.<\/p>\n<h4>The source<\/h4>\n<p>Without further ado, here is the full interface module DctmAPI.py:<\/p>\n<pre class=\"brush: python; gutter: true; first-line: 1\">\"\"\"\nThis module is a python - Documentum binding based on ctypes;\nrequires libdmcl40.so\/libdmcl.so to be reachable through LD_LIBRARY_PATH;\nC. Cervini - dbi-services.com\n\nThe binding works as-is for both python2 and python3; no recompilation required; that's the good thing with ctypes compared to e.g. distutils\/SWIG;\nUnder a 32-bit O\/S, it must use the libdmcl40.so, whereas under a 64-bit Linux it must use the java backed one, libdmcl.so;\n\nFor compatibility with python3 (where strings are now unicode ones and no longer arrays of bytes, ctypes strings parameters are always converted to unicode, either by prefixing them\nwith a b if litteral or by invoking their encode('ascii', 'ignore') method; to get back to text from bytes, b.decode() is used;these works in python2 as well as in python3 so the source is compatible with these two versions of the language;\n\"\"\"\n\nimport os\nimport ctypes\nimport sys, traceback\n\n# use foreign C library;\n# use this library in Content server = v6.x, 64-bit Linux;\n#dmlib = 'libdmcl.so'\n\ndm = 0\nlogLevel = 1\n\nclass getOutOfHere(Exception):\n   pass\n\ndef show(mesg):\n   \"displays the message mesg if allowed\"\n   if logLevel &gt; 0:\n      print(mesg)\n\ndef dmInit():\n   \"\"\"\n   initializes the Documentum part;\n   returns True if successfull, False otherwise;\n   \"\"\"\n\n   show(\"in dmInit()\")\n   global dm\n\n   try:\n      dm = ctypes.cdll.LoadLibrary(dmlib);  dm.restype = ctypes.c_char_p\n      show(\"dm=\" + str(dm) + \" after loading library \" + dmlib)\n      dm.dmAPIInit.restype    = ctypes.c_int;\n      dm.dmAPIDeInit.restype  = ctypes.c_int;\n      dm.dmAPIGet.restype     = ctypes.c_char_p;      dm.dmAPIGet.argtypes  = [ctypes.c_char_p]\n      dm.dmAPISet.restype     = ctypes.c_int;         dm.dmAPISet.argtypes  = [ctypes.c_char_p, ctypes.c_char_p]\n      dm.dmAPIExec.restype    = ctypes.c_int;         dm.dmAPIExec.argtypes = [ctypes.c_char_p]\n      status  = dm.dmAPIInit()\n   except Exception as e:\n      print(\"exception in dminit(): \")\n      print(e)\n      traceback.print_stack()\n      status = False\n   finally:\n      show(\"exiting dmInit()\")\n      return True if 0 != status else False\n   \ndef dmAPIDeInit():\n   \"\"\"\n   releases the memory structures in documentum's library;\n   returns True if no error, False otherwise;\n   \"\"\"\n   status = dm.dmAPIDeInit()\n   return True if 0 != status else False\n   \ndef dmAPIGet(s):\n   \"\"\"\n   passes the string s to dmAPIGet() method;\n   returns a non-empty string if OK, None otherwise;\n   \"\"\"\n   value = dm.dmAPIGet(s.encode('ascii', 'ignore'))\n   return value.decode() if value is not None else None\n\ndef dmAPISet(s, value):\n   \"\"\"\n   passes the string s to dmAPISet() method;\n   returns TRUE if OK, False otherwise;\n   \"\"\"\n   status = dm.dmAPISet(s.encode('ascii', 'ignore'), value.encode('ascii', 'ignore'))\n   return True if 0 != status else False\n\ndef dmAPIExec(stmt):\n   \"\"\"\n   passes the string s to dmAPIExec() method;\n   returns TRUE if OK, False otherwise;\n   \"\"\"\n   status = dm.dmAPIExec(stmt.encode('ascii', 'ignore'))\n   return True if 0 != status else False\n\ndef connect(docbase, user_name, password):\n   \"\"\"\n   connects to given docbase as user_name\/password;\n   returns a session id if OK, None otherwise\n   \"\"\"\n   show(\"in connect(), docbase = \" + docbase + \", user_name = \" + user_name + \", password = \" + password) \n   try:\n      session = dmAPIGet(\"connect,\" + docbase + \",\" + user_name + \",\" + password)\n      if session is None or not session:\n         raise(getOutOfHere)\n      else:\n         show(\"successful session \" + session)\n         show(dmAPIGet(\"getmessage,\" + session).rstrip())\n   except getOutOfHere:\n      print(\"unsuccessful connection to docbase \" + docbase + \" as user \" + user_name)\n      session = None\n   except Exception as e:\n      print(\"Exception in connect():\")\n      print(e)\n      traceback.print_stack()\n      session = None\n   finally:\n      show(\"exiting connect()\")\n      return session\n\ndef execute(session, dql_stmt):\n   \"\"\"\n   execute non-SELECT DQL statements;\n   returns TRUE if OK, False otherwise;\n   \"\"\"\n   show(\"in execute(), dql_stmt=\" + dql_stmt)\n   try:\n      query_id = dmAPIGet(\"query,\" + session + \",\" + dql_stmt)\n      if query_id is None:\n         raise(getOutOfHere)\n      err_flag = dmAPIExec(\"close,\" + session + \",\" + query_id)\n      if not err_flag:\n         raise(getOutOfHere)\n      status = True\n   except getOutOfHere:\n      show(dmAPIGet(\"getmessage,\" + session).rstrip())\n      status = False\n   except Exception as e:\n      print(\"Exception in execute():\")\n      print(e)\n      traceback.print_stack()\n      status = False\n   finally:\n      show(dmAPIGet(\"getmessage,\" + session).rstrip())\n      show(\"exiting execute()\")\n      return status\n\ndef select(session, dql_stmt, attribute_names):\n   \"\"\"\n   execute the DQL SELECT statement passed in dql_stmt and outputs the result to stdout;\n   attributes_names is a list of attributes to extract from the result set;\n   return True if OK, False otherwise;\n   \"\"\"\n   show(\"in select(), dql_stmt=\" + dql_stmt)\n   try:\n      query_id = dmAPIGet(\"query,\" + session + \",\" + dql_stmt)\n      if query_id is None:\n         raise(getOutOfHere)\n\n      s = \"\"\n      for attr in attribute_names:\n         s += \"[\" + attr + \"]t\"\n      print(s)\n      resp_cntr = 0\n      while dmAPIExec(\"next,\" + session + \",\" + query_id):\n         s = \"\"\n         for attr in attribute_names:\n            value = dmAPIGet(\"get,\" + session + \",\" + query_id + \",\" + attr)\n            if \"r_object_id\" == attr and value is None:\n               raise(getOutOfHere)\n            s += \"[\" + (value if value else \"None\") + \"]t\"\n         resp_cntr += 1\n         show(str(resp_cntr) + \": \" + s)\n      show(str(resp_cntr) + \" rows iterated\")\n\n      err_flag = dmAPIExec(\"close,\" + session + \",\" + query_id)\n      if not err_flag:\n         raise(getOutOfHere)\n\n      status = True\n   except getOutOfHere:\n      show(dmAPIGet(\"getmessage,\" + session).rstrip())\n      status = False\n   except Exception as e:\n      print(\"Exception in select():\")\n      print(e)\n      traceback.print_stack()\n      print(resp_cntr); print(attr); print(s); print(\"[\" + value + \"]\")\n      status = False\n   finally:\n      show(\"exiting select()\")\n      return status\n\ndef select2dict(session, dql_stmt, result):\n   \"\"\"\n   execute the DQL SELECT statement passed in dql_stmt and stores the result into result, an array of dictionaries;\n   return True if OK, False otherwise;\n   \"\"\"\n   show(\"in select2dict(), dql_stmt=\" + dql_stmt)\n\n   status = False\n   try:\n      query_id = dmAPIGet(\"query,\" + session + \",\" + dql_stmt)\n      if query_id is None:\n         raise(getOutOfHere)\n\n      # iterate through the result set;\n      row_counter = 0\n      attr_name = []\n      width = {}\n      while dmAPIExec(\"next,\" + session + \",\" + query_id):\n         result.append({})\n         nb_attrs = dmAPIGet(\"count,\" + session + \",\" + query_id)\n         if nb_attrs is None:\n            show(\"Error retrieving the count of returned attributes: \" + dmAPIGet(\"getmessage,\" + session))\n            raise(getOutOfHere)\n         nb_attrs = int(nb_attrs) \n         for i in range(nb_attrs):\n            if 0 == row_counter:\n               # get the attributes' names only once for the whole query;\n               value = dmAPIGet(\"get,\" + session + \",\" + query_id + \",_names[\" + str(i) + \"]\")\n               if value is None:\n                  show(\"error while getting the attribute name at position \" + str(i) + \": \" + dmAPIGet(\"getmessage,\" + session))\n                  raise(getOutOfHere)\n               attr_name.append(value)\n               if value in width:\n                  width[value] = max(width[attr_name[i]], len(value))\n               else:\n                  width[value] = len(value)\n\n            is_repeating = dmAPIGet(\"repeating,\" + session + \",\" + query_id + \",\" + attr_name[i])\n            if is_repeating is None:\n               show(\"error while getting the arity of attribute \" + attr_name[i] + \": \" + dmAPIGet(\"getmessage,\" + session))\n               raise(getOutOfHere)\n            is_repeating = int(is_repeating)\n\n            if 1 == is_repeating:\n               # multi-valued attributes;\n               result[row_counter] [attr_name[i]] = []\n               count = dmAPIGet(\"values,\" + session + \",\" + query_id + \",\" + attr_name[i])\n               if count is None:\n                  show(\"error while getting the arity of attribute \" + attr_name[i] + \": \" + dmAPIGet(\"getmessage,\" + session))\n                  raise(getOutOfHere)\n               count = int(count)\n\n               for j in range(count):\n                  value = dmAPIGet(\"get,\" + session + \",\" + query_id + \",\" + attr_name[i] + \"[\" + str(j) + \"]\")\n                  if value is None:\n                     value = \"null\"\n                  result[row_counter] [attr_name[i]].append(value)\n            else:\n               # mono-valued attributes;\n               value = dmAPIGet(\"get,\" + session + \",\" + query_id + \",\" + attr_name[i])\n               if value is None:\n                  value = \"null\"\n               width[attr_name[i]] = len(attr_name[i])\n               result[row_counter][attr_name[i]] = value\n         row_counter += 1\n      err_flag = dmAPIExec(\"close,\" + session + \",\" + query_id)\n      if not err_flag:\n         show(\"Error closing the query collection: \" + dmAPIGet(\"getmessage,\" + session))\n         raise(getOutOfHere)\n\n      status = True\n\n   except getOutOfHere:\n      show(dmAPIGet(\"getmessage,\" + session).rstrip())\n      status = False\n   except Exception as e:\n      print(\"Exception in select2dict():\")\n      print(e)\n      traceback.print_stack()\n      status = False\n   finally:\n      return status\n\ndef disconnect(session):\n   \"\"\"\n   closes the given session;\n   returns True if no error, False otherwise;\n   \"\"\"\n   show(\"in disconnect()\")\n   try:\n      status = dmAPIExec(\"disconnect,\" + session)\n   except Exception as e:\n      print(\"Exception in disconnect():\")\n      print(e)\n      traceback.print_stack()\n      status = False\n   finally:\n      show(\"exiting disconnect()\")\n      return status\n\n# initializes the interface;\ndmInit()\n<\/pre>\n<p>The select2dict() function is an enhancement of select(). It dynamically gets the list of attributes SELECTed, so no need to provide one. In addition, instead of printing it, it returns the resultset into an array of dictionaries, which is handy if some further processing is required later.<\/p>\n<h4>The test script<\/h4>\n<p>Here is an example of script showing how to use the interface:<\/p>\n<pre class=\"brush: actionscript3; gutter: true; first-line: 1\">#!\/usr\/bin\/env python\n\n\"\"\"\nTest the ctypes-based python interface to Documentum API;\n\"\"\"\n\nimport DctmAPI\n\n# -----------------\n# main;\nif __name__ == \"__main__\":\n   DctmAPI.logLevel = 1\n\n   # not really needed as it is done in the module itself;\n   status = DctmAPI.dmInit()\n   if status:\n      print(\"dmInit() was successful\")\n   else:\n      print(\"dmInit() was not successful\")\n\n   print(\"\")\n   session = DctmAPI.connect(docbase = \"dmtest\", user_name = \"dmadmin\", password = \"dmadmin\")\n   if session is None:\n      print(\"no session opened, exiting ...\")\n      exit(1)\n   \n   print(\"\")\n   dump = DctmAPI.dmAPIGet(\"dump,\" + session + \",\" + \"0900c35080008107\")\n   print(\"object 0900c35080008107 dumped:n\" + dump)\n   \n   print(\"\")\n   stmt = \"update dm_document object set language_code = 'FR' where r_object_id = '0900c35080008107'\"\n   status = DctmAPI.execute(session, stmt)\n   if status:\n      print(\"execute [\" + stmt + \"] was successful\")\n   else:\n      print(\"execute [\" + stmt + \"] was not successful\")\n\n   print(\"\")\n   stmt = \"select r_object_id, object_name, owner_name, acl_domain, acl_name from dm_document\"\n   status = DctmAPI.select(session, stmt, (\"r_object_id\", \"object_name\", \"owner_name\", \"acl_domain\", \"acl_name\"))\n   if status:\n      print(\"select [\" + stmt + \"] was successful\")\n   else:\n      print(\"select [\" + stmt + \"] was not successful\")\n\n   print(\"\")\n   stmt = \"select count(*) from dm_document\"\n   status = DctmAPI.select(session, stmt,  [\"count(*)\"])\n   if status:\n      print(\"select [\" + stmt + \"] was successful\")\n   else:\n      print(\"select [\" + stmt + \"] was not successful\")\n\n   print(\"\")\n   status = DctmAPI.disconnect(session)\n   if status:\n      print(\"successfully disconnected\")\n   else:\n      print(\"error while  disconnecting\")\n\n   print(\"\")\n   status = DctmAPI.dmAPIDeInit()\n   if status:\n      print(\"successfully deInited\")\n   else:\n      print(\"error while  deInited\")\n<\/pre>\n<p>I&#8217;m not a day to day user of python so I guess there are ways to make the interface more idiomatic, or pythonic as they say. Feel free to adapt it to your tastes and needs. Comments and suggestions are welcome of course.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Many years ago, out of frustration by the poorness of scripting tools in Documentum, I realized a Documentum binding for python using the distutils and I remember how easy and straightforward it had been, even for someone not really into these things on a daily basis. Recently, I wanted to reuse that work but couldn&#8217;t [&hellip;]<\/p>\n","protected":false},"author":40,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[525],"tags":[],"type_dbi":[],"class_list":["post-11252","post","type-post","status-publish","format-standard","hentry","category-enterprise-content-management"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.2) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Adding a Documentum extension into python - dbi Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Adding a Documentum extension into python\" \/>\n<meta property=\"og:description\" content=\"Many years ago, out of frustration by the poorness of scripting tools in Documentum, I realized a Documentum binding for python using the distutils and I remember how easy and straightforward it had been, even for someone not really into these things on a daily basis. Recently, I wanted to reuse that work but couldn&#8217;t [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/\" \/>\n<meta property=\"og:site_name\" content=\"dbi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-05-22T11:15:03+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-10-24T07:22:44+00:00\" \/>\n<meta name=\"author\" content=\"Middleware Team\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Middleware Team\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/\"},\"author\":{\"name\":\"Middleware Team\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d8563acfc6e604cce6507f45bac0ea1\"},\"headline\":\"Adding a Documentum extension into python\",\"datePublished\":\"2018-05-22T11:15:03+00:00\",\"dateModified\":\"2025-10-24T07:22:44+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/\"},\"wordCount\":1378,\"commentCount\":0,\"articleSection\":[\"Enterprise content management\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/\",\"name\":\"Adding a Documentum extension into python - dbi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\"},\"datePublished\":\"2018-05-22T11:15:03+00:00\",\"dateModified\":\"2025-10-24T07:22:44+00:00\",\"author\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d8563acfc6e604cce6507f45bac0ea1\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\/\/www.dbi-services.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Adding a Documentum extension into python\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/\",\"name\":\"dbi Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d8563acfc6e604cce6507f45bac0ea1\",\"name\":\"Middleware Team\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/ddcae7ba0f9d1a0e7ae707f0e689e4a9c95bb48ec49c8e6d9cc86d43f4121cb6?s=96&d=mm&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/ddcae7ba0f9d1a0e7ae707f0e689e4a9c95bb48ec49c8e6d9cc86d43f4121cb6?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/ddcae7ba0f9d1a0e7ae707f0e689e4a9c95bb48ec49c8e6d9cc86d43f4121cb6?s=96&d=mm&r=g\",\"caption\":\"Middleware Team\"},\"url\":\"https:\/\/www.dbi-services.com\/blog\/author\/middleware-team\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Adding a Documentum extension into python - dbi Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/","og_locale":"en_US","og_type":"article","og_title":"Adding a Documentum extension into python","og_description":"Many years ago, out of frustration by the poorness of scripting tools in Documentum, I realized a Documentum binding for python using the distutils and I remember how easy and straightforward it had been, even for someone not really into these things on a daily basis. Recently, I wanted to reuse that work but couldn&#8217;t [&hellip;]","og_url":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/","og_site_name":"dbi Blog","article_published_time":"2018-05-22T11:15:03+00:00","article_modified_time":"2025-10-24T07:22:44+00:00","author":"Middleware Team","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Middleware Team","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/#article","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/"},"author":{"name":"Middleware Team","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d8563acfc6e604cce6507f45bac0ea1"},"headline":"Adding a Documentum extension into python","datePublished":"2018-05-22T11:15:03+00:00","dateModified":"2025-10-24T07:22:44+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/"},"wordCount":1378,"commentCount":0,"articleSection":["Enterprise content management"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/","url":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/","name":"Adding a Documentum extension into python - dbi Blog","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/#website"},"datePublished":"2018-05-22T11:15:03+00:00","dateModified":"2025-10-24T07:22:44+00:00","author":{"@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d8563acfc6e604cce6507f45bac0ea1"},"breadcrumb":{"@id":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.dbi-services.com\/blog\/adding-a-documentum-extension-into-python\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/www.dbi-services.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Adding a Documentum extension into python"}]},{"@type":"WebSite","@id":"https:\/\/www.dbi-services.com\/blog\/#website","url":"https:\/\/www.dbi-services.com\/blog\/","name":"dbi Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d8563acfc6e604cce6507f45bac0ea1","name":"Middleware Team","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/ddcae7ba0f9d1a0e7ae707f0e689e4a9c95bb48ec49c8e6d9cc86d43f4121cb6?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/ddcae7ba0f9d1a0e7ae707f0e689e4a9c95bb48ec49c8e6d9cc86d43f4121cb6?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/ddcae7ba0f9d1a0e7ae707f0e689e4a9c95bb48ec49c8e6d9cc86d43f4121cb6?s=96&d=mm&r=g","caption":"Middleware Team"},"url":"https:\/\/www.dbi-services.com\/blog\/author\/middleware-team\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/11252","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/users\/40"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/comments?post=11252"}],"version-history":[{"count":1,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/11252\/revisions"}],"predecessor-version":[{"id":41167,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/11252\/revisions\/41167"}],"wp:attachment":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media?parent=11252"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/categories?post=11252"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/tags?post=11252"},{"taxonomy":"type","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/type_dbi?post=11252"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}