# XECM
This python library calls the Opentext Extended ECM REST API.
The API documentation is available on [OpenText Developer](https://developer.opentext.com/ce/products/extendedecm)
A detailed documentation of this package is available [on GitHub](https://github.com/fitschgo/xecm).
Our Homepage is: [xECM SuccessFactors Knowledge](https://www.xecm-successfactors.com/xecm-knowledge.html)
# Quick start
Install "xecm":
```bash
pip install xecm
```
## Start using the xecm package
```python
import xecm
import logging
logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO) # use logging.ERROR to reduce logging
if __name__ == '__main__':
deflogger = logging.getLogger("mylogger")
cshost = 'http://otcs.phil.local'
dshost = 'http://otds.phil.local'
# get OTCSTicket with username and password
csapi = xecm.CSRestAPI(xecm.LoginType.OTCS_TICKET, f'{cshost}/otcs/cs.exe', 'myuser', 's#cret', True, deflogger)
# get OTDSTicket with username and password
csapi = xecm.CSRestAPI(xecm.LoginType.OTDS_TICKET, dshost, 'myuser@partition', 's#cret', True, deflogger)
# get OTDS Bearer Token with client id and client secret
csapi = xecm.CSRestAPI(xecm.LoginType.OTDS_BEARER, dshost, 'oauth-user', 'gU5p8....4KZ', True, deflogger)
# ...
nodeId = 130480
try:
res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name', 'type', 'type_name'], False, False, False)
print(res)
# {
# 'properties': {'id': 130480, 'name': 'Bewerbung-Phil-Egger-2020.pdf', 'type': 144, 'type_name': 'Document'},
# 'categories': [],
# 'permissions': {'owner': {}, 'group': {}, 'public': {}, 'custom': []},
# 'classifications': []
# }
except xecm.LoginTimeoutException as lex:
print(f'Ticket has been invalidated since last login (timeout) - do a re-login: {lex}')
except Exception as gex:
print(f'General Error: {gex}')
```
## Available Logins: OTCSTicket, OTDSTicket or OTDS Bearer Token
```python
# get OTCSTicket with username and password
csapi = xecm.CSRestAPI(xecm.LoginType.OTCS_TICKET, cshost, 'myuser', 's#cret', deflogger)
# get OTDSTicket with username and password
csapi = xecm.CSRestAPI(xecm.LoginType.OTDS_TICKET, dshost, 'myuser@partition', 's#cret', deflogger)
# get OTDS Bearer Token with client id and client secret
csapi = xecm.CSRestAPI(xecm.LoginType.OTDS_BEARER, dshost, 'oauth-user', 'gU5p8....4KZ', deflogger)
```
## Node Functions (folder, document, ...)
```python
# get node information - min -> load only some fields
res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name', 'type', 'type_name'], False, False, False)
# get node information - max -> load all fields, incl. categories, incl. permissions, incl. classifications
res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, [], True, True, True)
# get sub nodes - min
res = csapi.subnodes_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, False, False, 1) # page 1 contains 200 sub items
# get sub nodes - load categories
res = csapi.subnodes_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], True, False, False, 1) # page 1 contains 20 sub items
# get sub nodes - load permissions
res = csapi.subnodes_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, True, False, 1) # page 1 contains 20 sub items
# get sub nodes - load classifications
res = csapi.subnodes_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, False, True, 1) # page 1 contains 10 sub items
# filter subnodes
res = csapi.subnodes_filter(f'{cshost}/otcs/cs.exe', 30622, 'OTHCM_WS_Employee_Categories', False, True)
# search nodes
res = csapi.search(f'{cshost}/otcs/cs.exe', 'Documents', 0, baseFolderId, 1)
# get details of several nodes - max 250 entries
res = csapi.nodes_get_details(f'{cshost}/otcs/cs.exe', [ 30724, 30728, 30729 ])
# create new node - min
res = csapi.node_create(f'{cshost}/otcs/cs.exe', parentId, 0, 'test', 'test', {}, {} )
# create new node - with multiple metadata names
res = csapi.node_create(f'{cshost}/cs/cs.exe', nodeId, 0, 'test', 'test', { 'en': 'test en', 'de': 'test de'}, { 'en': 'desc en', 'de': 'desc de'} )
# update name and description of a node (folder, document, ...) - min
res = csapi.node_update(f'{cshost}/cs/cs.exe', nodeId, 0, 'test1', 'desc1', {}, {}, {})
# move node and apply categories
cats = { '1279234_2': 'test' }
res = csapi.node_update(f'{cshost}/cs/cs.exe', nodeId, newDestId, '', '', {}, {}, cats)
# delete a node
res = csapi.node_delete(f'{cshost}/cs/cs.exe', nodeId)
# download a document into file system
res = csapi.node_download_file(f'{cshost}/otcs/cs.exe', nodeId, '', '/home/fitsch/Downloads', 'test-download.pdf')
# download a document as base64 string
res = csapi.node_download_bytes(f'{cshost}/otcs/cs.exe', nodeId, '')
# {'message', 'file_size', 'base64' }
# upload a document from file system
res = csapi.node_upload_file(f'{cshost}/otcs/cs.exe', nodeId, '/home/fitsch/Downloads', 'test-download.pdf', 'test-upload.pdf', { '30724_2': '2020-03-17' })
# upload a document from byte array
barr = open('/home/fitsch/Downloads/test-download.pdf', 'rb').read()
res = csapi.node_upload_bytes(f'{cshost}/otcs/cs.exe', nodeId, barr, 'test-upload.pdf', {'30724_2': '2020-03-17'})
# covert a Content Server path to a Node ID
res = csapi.path_to_id(f'{cshost}/otcs/cs.exe', 'Content Server Categories:SuccessFactors:OTHCM_WS_Employee_Categories:Personal Information')
# get all volumes in Content Server
res = csapi.volumes_get(f'{cshost}/otcs/cs.exe')
# [
# {
# 'properties':
# {
# 'id': 2006,
# 'name': 'Content Server Categories'
# }
# },
# {
# 'properties':
# {
# 'id': 2000,
# 'name': 'Enterprise'
# }
# },
# ...
# ]
```
## Category Functions (Metadata)
```python
# get node information and load categories
res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], True, False, False)
# add category to node
res = csapi.node_category_add(f'{cshost}/otcs/cs.exe', nodeId, { "category_id": 32133, "32133_2": "8000", "32133_39": ["test 1", "test 2"], "32133_33_1_34": "Org Unit 1", "32133_33_1_35": "Org Unit Desc 1", "32133_33_2_34": "Org Unit 2", "32133_33_2_35": "Org Unit Desc 2" } )
# update category on a node
res = csapi.node_category_update(f'{cshost}/otcs/cs.exe', nodeId, 32133, { "32133_2": "8000", "32133_39": ["test 1", "test 2"], "32133_33_1_34": "Org Unit 1", "32133_33_1_35": "Org Unit Desc 1", "32133_33_2_34": "Org Unit 2", "32133_33_2_35": "Org Unit Desc 2" } )
# delete category from a node
res = csapi.node_category_delete(f'{cshost}/otcs/cs.exe', nodeId, 32133)
# read all category attributes - use i.e. path_to_id() to get cat_id
res = csapi.category_get_mappings(f'{cshost}/otcs/cs.exe', cat_id)
# {
# 'main_name': 'Job Information',
# 'main_id': 32133,
# 'map_names':
# {
# 'Company Code': '32133_2',
# 'Company Code Description': '32133_3',
# ...
# },
# 'map_ids':
# {
# '32133_2': 'Company Code',
# '32133_3': 'Company Code Description',
# ...
# }
# }
# get category information for a specific attribute
res = csapi.category_attribute_id_get(f'{cshost}/otcs/cs.exe', 'Content Server Categories:SuccessFactors:OTHCM_WS_Employee_Categories:Personal Information', 'User ID')
# {
# 'category_id': 30643,
# 'category_name': 'Personal Information',
# 'attribute_key': '30643_26',
# 'attribute_name': 'User ID'
# }
```
## Classification Functions
```python
# get node information and load classifications
res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, False, True)
# apply classifications to node
res = csapi.node_classifications_apply(f'{cshost}/otcs/cs.exe', nodeId, False, [120571,120570])
# same function to remove classification 120570 from node
res = csapi.node_classifications_apply(f'{cshost}/otcs/cs.exe', nodeId, False, [120571])
```
## Permission Functions
```python
# get node information and load permissions
res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, True, False)
# apply owner permissions on node
res = csapi.node_permissions_owner_apply(f'{cshost}/otcs/cs.exe', nodeId, { "permissions":["delete","delete_versions","edit_attributes","edit_permissions","modify","reserve","see","see_contents"], "right_id": 1000 })
# delete owner permission from node
res = csapi.node_permissions_owner_delete(f'{cshost}/otcs/cs.exe', nodeId)
# apply group permissions on node
res = csapi.node_permissions_group_apply(f'{cshost}/otcs/cs.exe', nodeId, {"permissions":["delete","delete_versions","edit_attributes","edit_permissions","modify","reserve","see","see_contents"], "right_id": 2001 })
# delete group permission from node
res = csapi.node_permissions_group_delete(f'{cshost}/otcs/cs.exe', nodeId)
# apply public permissions on node
res = csapi.node_permissions_public_apply(f'{cshost}/otcs/cs.exe', nodeId, {"permissions":["delete","delete_versions","edit_attributes","edit_permissions","modify","reserve","see","see_contents"] })
# delete public permission from node
res = csapi.node_permissions_public_delete(f'{cshost}/otcs/cs.exe', nodeId)
# apply a new custom permissions on node
res = csapi.node_permissions_custom_apply(f'{cshost}/otcs/cs.exe', nodeId, [{"permissions":["see","see_contents"], "right_id": 1001 }])
# update an existing custom permissions on node
res = csapi.node_permissions_custom_update(f'{cshost}/otcs/cs.exe', nodeId, 2001, {"permissions":["delete","delete_versions","edit_attributes","edit_permissions","modify","reserve","see","see_contents"] })
# delete a custom permissions from node
res = csapi.node_permissions_custom_delete(f'{cshost}/otcs/cs.exe', nodeId, 1001)
```
## Smart Document Types Functions
```python
# get all smart document types
res = csapi.smartdoctypes_get_all(f'{cshost}/otcs/cs.exe')
for smartdoctype in res:
print(f"{smartdoctype['workspace_template_names']} - {smartdoctype['dataId']} - {smartdoctype['name']} --> {smartdoctype['classification_id']} - {smartdoctype['classification_name']}")
# get rules of a smart document type
smartDocTypeId = smartdoctype['dataId']
res = csapi.smartdoctypes_rules_get(f'{cshost}/otcs/cs.exe', smartDocTypeId)
for smartdoctype in res:
print(f"{smartdoctype['template_name']} ({smartdoctype['template_id']}) - {smartdoctype['smartdocumenttype_id']} - RuleID: {smartdoctype['rule_id']} / DocGen: {smartdoctype['document_generation']} --> Classification: {smartdoctype['classification_id']} --> Location: {smartdoctype['location']}")
# get rule detail
ruleId = smartdoctype['rule_id']
res = csapi.smartdoctype_rule_detail_get(f'{cshost}/otcs/cs.exe', ruleId)
for rule_tab in res:
print(f"tab: {rule_tab['bot_key']} - data: {rule_tab['data']}")
# create smart document type under "Smart Document Types" root folder 6004 (id is different per system) -> see get_volumes() function
res = csapi.smartdoctype_add(f'{cshost}/otcs/cs.exe', 6004, categoryId, 'smart doc test')
# add workspace template to rule
res = csapi.smartdoctype_workspacetemplate_add(f'{cshost}/otcs/cs.exe', smartDocTypeId, classificationId, templateId)
# {
# 'is_othcm_template': True,
# 'ok': True,
# 'rule_id': 11,
# 'statusCode': 200
# }
# add workspace template to rule -> get locationId with path_to_id() function
location = csapi.path_to_id(f'{cshost}/otcs/cs.exe', 'Content Server Document Templates:SuccessFactors:Employee CHE:01 Entry Documents:110 Recruiting / Application')
# {'id': 120603, 'name': '110 Recruiting / Application'}
locationId = location.get('id', 0)
res = csapi.smartdoctype_rule_context_save(f'{cshost}/otcs/cs.exe', ruleId, categoryId, locationId, 'update')
# {
# 'ok': True,
# 'statusCode': 200,
# 'updatedAttributeIds': [2],
# 'updatedAttributeNames': ['Date of Origin']
# }
# add 'mandatory' tab in rule
res = csapi.smartdoctype_rule_mandatory_save(f'{cshost}/otcs/cs.exe', ruleId, True, 'add')
# update 'mandatory' tab in rule
res = csapi.smartdoctype_rule_mandatory_save(f'{cshost}/otcs/cs.exe', ruleId, False, 'update')
# delete 'mandatory' tab in rule
res = csapi.smartdoctype_rule_mandatory_delete(f'{cshost}/otcs/cs.exe', ruleId)
# add 'document expiration' tab in rule
res = csapi.smartdoctype_rule_documentexpiration_save(f'{cshost}/otcs/cs.exe', ruleId, True, 2, 0, 6, 'add')
# update 'document expiration' tab in rule
res = csapi.smartdoctype_rule_documentexpiration_save(f'{cshost}/otcs/cs.exe', ruleId, False, 2, 0, 4, 'update')
# delete 'document expiration' tab in rule
res = csapi.smartdoctype_rule_documentexpiration_delete(f'{cshost}/otcs/cs.exe', ruleId)
# add 'document generation' tab in rule
res = csapi.smartdoctype_rule_generatedocument_save(f'{cshost}/otcs/cs.exe', ruleId, True, 'add')
# update 'document generation' tab in rule
res = csapi.smartdoctype_rule_generatedocument_save(f'{cshost}/otcs/cs.exe', ruleId, False, 'update')
# delete 'document generation' tab in rule
res = csapi.smartdoctype_rule_generatedocument_delete(f'{cshost}/otcs/cs.exe', ruleId)
# add 'allow upload' tab in rule
res = csapi.smartdoctype_rule_allowupload_save(f'{cshost}/otcs/cs.exe', ruleId, [2001], 'add')
# update 'allow upload' tab in rule
res = csapi.smartdoctype_rule_allowupload_save(f'{cshost}/otcs/cs.exe', ruleId, [2001,120593], 'update')
# delete 'allow upload' tab in rule
res = csapi.smartdoctype_rule_allowupload_delete(f'{cshost}/otcs/cs.exe', ruleId)
# add 'upload approval' tab in rule
res = csapi.smartdoctype_rule_uploadapproval_save(f'{cshost}/otcs/cs.exe', ruleId, True, workflowMapId, [{'wfrole': 'Approver', 'member': 2001 }], 'add')
# update 'allow upload' tab in rule
res = csapi.smartdoctype_rule_uploadapproval_save(f'{cshost}/otcs/cs.exe', ruleId, True, workflowMapId, [{'wfrole': 'Approver', 'member': 120593 }], 'update')
# delete 'allow upload' tab in rule
res = csapi.smartdoctype_rule_uploadapproval_delete(f'{cshost}/otcs/cs.exe', ruleId)
```
## Business Workspace Functions
```python
# get business workspace node id by business object type and business object id
res = csapi.businessworkspace_search(f'{cshost}/otcs/cs.exe', 'SuccessFactors', 'sfsf:user', 'Z70080539', 1)
# get customized smart document types for business workspace
# bws_id from businessworkspace_search()
res = csapi.businessworkspace_smartdoctypes_get(f'{cshost}/otcs/cs.exe', bws_id)
# [{'classification_id': 120571, 'classification_name': 'Application Documents', 'classification_description': '', 'category_id': 6002, 'location': '122061:122063', 'document_generation': 0, 'required': 0, 'template_id': 120576}, ...]
# get category definition for smart document type to be used for document upload into business workspace
# bws_id from businessworkspace_search()
# cat_id from businessworkspace_smartdoctypes_get()
res = csapi.businessworkspace_categorydefinition_for_upload_get(f'{cshost}/otcs/cs.exe', bws_id, cat_id)
# upload file using smart document type into business workspace
res = csapi.businessworkspace_hr_upload_file(f'{cshost}/otcs/cs.exe', bws_id, '/home/fitsch/Downloads', 'test-download.pdf', 'application.pdf', class_dict['classification_id'], cat_id, cat_dict)
##### ########################## #####
##### snippet for upload process #####
##### ########################## #####
res = csapi.businessworkspace_search(f'{cshost}/otcs/cs.exe', 'SuccessFactors', 'sfsf:user', 'Z70080539', 1)
bws_id = -1
class_name = 'Application Documents'
class_dict = {}
cat_id = -1
cat_attr_date_of_origin = ''
cat_dict = {}
date_of_origin = datetime(2020, 5, 17)
# res = {'results': [{'id': 122051, 'name': 'Employee Z70080539 Phil Egger', 'parent_id': 30648}, ... ], 'page_total': 1}
if res and res.get('results', []) and len(res.get('results', [])) > 0:
bws_id = res['results'][0].get('id', -1)
if bws_id > 0:
res = csapi.businessworkspace_smartdoctypes_get(f'{cshost}/otcs/cs.exe', bws_id)
# res = [{'classification_id': 120571, 'classification_name': 'Application Documents', 'classification_description': '', 'category_id': 6002, 'location': '122061:122063', 'document_generation': 0, 'required': 0, 'template_id': 120576}, ... ]
if res:
for class_def in res:
if class_def['classification_name'] == class_name:
class_dict = class_def
break
if class_dict:
# class_dict = {'classification_id': 120571, 'classification_name': 'Application Documents', 'classification_description': '', 'category_id': 6002, 'location': '122061:122063', 'document_generation': 0, 'required': 0, 'template_id': 120576}
res = csapi.businessworkspace_categorydefinition_for_upload_get(f'{cshost}/otcs/cs.exe', bws_id, class_dict['category_id'])
# res = [{'data': {'category_id': 6002, '6002_2': None}, 'options': {}, 'form': {}, 'schema': {'properties': {'category_id': {'readonly': False, 'required': False, 'title': 'Document Type Details', 'type': 'integer'}, '6002_2': {'readonly': False, 'required': False, 'title': 'Date of Origin', 'type': 'date'}}, 'type': 'object'}}]
if res and len(res) > 0:
if res[0].get('schema', {}) and res[0]['schema'].get('properties', {}):
# res[0]['schema']['properties'] = {'category_id': {'readonly': False, 'required': False, 'title': 'Document Type Details', 'type': 'integer'}, '6002_2': {'readonly': False, 'required': False, 'title': 'Date of Origin', 'type': 'date'}}
cat_id = class_dict['category_id']
for p in res[0]['schema']['properties']:
if str(cat_id) in p and res[0]['schema']['properties'][p].get('type', '') == 'date' and 'Origin' in res[0]['schema']['properties'][p].get('title', ''):
cat_attr_date_of_origin = p
break
if cat_id > 0 and cat_attr_date_of_origin:
cat_dict = { cat_attr_date_of_origin: date_of_origin.isoformat() }
else:
deflogger.info(f'Date Of Origin not found in Category {class_dict['category_id']} for Workspace {bws_id}')
try:
res = csapi.businessworkspace_hr_upload_file(f'{cshost}/otcs/cs.exe', bws_id, '/home/fitsch/Downloads', 'test-download.pdf', 'application.pdf', class_dict['classification_id'], cat_id, cat_dict)
if res > 0:
deflogger.info(f'File successfully uploaded - {res}')
else:
raise Exception(f'Invalid Node ID returned: {res}')
except Exception as innerErr:
deflogger.error(f'File failed to upload {innerErr}')
else:
deflogger.error(f'Classification Definition not found for {class_name} in Workspace {bws_id}')
```
## WebReport Functions
```python
# call web report by nickname using parameters
res = csapi.webreport_nickname_call(f'{cshost}/otcs/cs.exe', 'WR_API_Test', {'p_name': 'name', 'p_desc': 'description'})
# call web report by node id using parameters
res = csapi.webreport_nodeid_call(f'{cshost}/otcs/cs.exe', wr_id, {'p_name': 'name', 'p_desc': 'description'})
```
## Server Information Functions
```python
# ping Content Server
res = csapi.ping(f'{cshost}/otcs/cs.exe')
# get server info (version, metadata languages, ...)
res = csapi.server_info(f'{cshost}/otcs/cs.exe')
print(f"Version: {res['server']['version']}")
print('Metadata Languages:')
for lang in res['server']['metadata_languages']:
print(f"{lang['language_code']} - {lang['display_name']}")
```
## Basic API Functions - in case that something is not available in this class
```python
# GET API Call
res = csapi.call_get(f'{cshost}/otcs/cs.exe/api/v1/nodes/2000/classifications')
# POST API Call using form-url-encoded -> i.e. do fancy search
res = csapi.call_post_form_url_encoded(f'{cshost}/otcs/cs.exe/api/v2/search', { 'body': json.dumps({ 'where': 'OTName: "Personal Information" and OTSubType: 131 and OTLocation: 2006' })})
# POST API Call using form-data -> can be used to upload files
data = { 'type': 144, 'parent_id': parent_id, 'name': remote_filename }
params = { 'body' : json.dumps(data) }
files = {'file': (remote_filename, open(os.path.join(local_folder, local_filename), 'rb'), 'application/octet-stream')}
res = csapi.call_post_form_data(f'{cshost}/otcs/cs.exe/api/v2/nodes', params, files)
# PUT API Call
params = {'body': json.dumps(category)}
res = self.call_put(f'{cshost}/otcs/cs.exe/api/v2/nodes/{node_id}/categories/{category_id}', params)
# DELETE API Call
res = self.call_delete(f'{cshost}/otcs/cs.exe/api/v2/nodes/{node_id}/categories/{category_id}')
```
# Disclaimer
Copyright © 2025 by Philipp Egger, All Rights Reserved. The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Raw data
{
"_id": null,
"home_page": null,
"name": "xecm",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "xecm, opentext, extendedecm, contentserver, otcs, otds",
"author": null,
"author_email": "Philipp Egger <philipp.egger@handel-it.com>",
"download_url": "https://files.pythonhosted.org/packages/ea/27/aad3e0403ad18da38f4eab919da18680cc978a6d3bac0017c6232d4514cb/xecm-25.1.0.tar.gz",
"platform": null,
"description": "# XECM\n\nThis python library calls the Opentext Extended ECM REST API.\nThe API documentation is available on [OpenText Developer](https://developer.opentext.com/ce/products/extendedecm)\nA detailed documentation of this package is available [on GitHub](https://github.com/fitschgo/xecm).\nOur Homepage is: [xECM SuccessFactors Knowledge](https://www.xecm-successfactors.com/xecm-knowledge.html)\n\n# Quick start\n\nInstall \"xecm\":\n\n```bash\npip install xecm\n```\n\n## Start using the xecm package\n```python\nimport xecm\nimport logging\n\nlogging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO) # use logging.ERROR to reduce logging\n\nif __name__ == '__main__':\n deflogger = logging.getLogger(\"mylogger\")\n cshost = 'http://otcs.phil.local'\n dshost = 'http://otds.phil.local'\n\n # get OTCSTicket with username and password\n csapi = xecm.CSRestAPI(xecm.LoginType.OTCS_TICKET, f'{cshost}/otcs/cs.exe', 'myuser', 's#cret', True, deflogger)\n\n # get OTDSTicket with username and password\n csapi = xecm.CSRestAPI(xecm.LoginType.OTDS_TICKET, dshost, 'myuser@partition', 's#cret', True, deflogger)\n\n # get OTDS Bearer Token with client id and client secret\n csapi = xecm.CSRestAPI(xecm.LoginType.OTDS_BEARER, dshost, 'oauth-user', 'gU5p8....4KZ', True, deflogger)\n\n # ...\n\n nodeId = 130480\n try:\n res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name', 'type', 'type_name'], False, False, False)\n print(res)\n # {\n # 'properties': {'id': 130480, 'name': 'Bewerbung-Phil-Egger-2020.pdf', 'type': 144, 'type_name': 'Document'}, \n # 'categories': [], \n # 'permissions': {'owner': {}, 'group': {}, 'public': {}, 'custom': []}, \n # 'classifications': []\n # }\n except xecm.LoginTimeoutException as lex:\n print(f'Ticket has been invalidated since last login (timeout) - do a re-login: {lex}')\n except Exception as gex:\n print(f'General Error: {gex}')\n\n```\n\n## Available Logins: OTCSTicket, OTDSTicket or OTDS Bearer Token\n```python\n # get OTCSTicket with username and password\n csapi = xecm.CSRestAPI(xecm.LoginType.OTCS_TICKET, cshost, 'myuser', 's#cret', deflogger)\n\n # get OTDSTicket with username and password\n csapi = xecm.CSRestAPI(xecm.LoginType.OTDS_TICKET, dshost, 'myuser@partition', 's#cret', deflogger)\n\n # get OTDS Bearer Token with client id and client secret\n csapi = xecm.CSRestAPI(xecm.LoginType.OTDS_BEARER, dshost, 'oauth-user', 'gU5p8....4KZ', deflogger)\n```\n\n## Node Functions (folder, document, ...)\n```python\n # get node information - min -> load only some fields\n res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name', 'type', 'type_name'], False, False, False)\n\n # get node information - max -> load all fields, incl. categories, incl. permissions, incl. classifications\n res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, [], True, True, True)\n\n # get sub nodes - min\n res = csapi.subnodes_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, False, False, 1) # page 1 contains 200 sub items\n\n # get sub nodes - load categories\n res = csapi.subnodes_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], True, False, False, 1) # page 1 contains 20 sub items\n\n # get sub nodes - load permissions\n res = csapi.subnodes_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, True, False, 1) # page 1 contains 20 sub items\n\n # get sub nodes - load classifications\n res = csapi.subnodes_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, False, True, 1) # page 1 contains 10 sub items\n\n # filter subnodes\n res = csapi.subnodes_filter(f'{cshost}/otcs/cs.exe', 30622, 'OTHCM_WS_Employee_Categories', False, True)\n\n # search nodes\n res = csapi.search(f'{cshost}/otcs/cs.exe', 'Documents', 0, baseFolderId, 1)\n\n # get details of several nodes - max 250 entries\n res = csapi.nodes_get_details(f'{cshost}/otcs/cs.exe', [ 30724, 30728, 30729 ])\n\n # create new node - min\n res = csapi.node_create(f'{cshost}/otcs/cs.exe', parentId, 0, 'test', 'test', {}, {} )\n\n # create new node - with multiple metadata names\n res = csapi.node_create(f'{cshost}/cs/cs.exe', nodeId, 0, 'test', 'test', { 'en': 'test en', 'de': 'test de'}, { 'en': 'desc en', 'de': 'desc de'} )\n \n # update name and description of a node (folder, document, ...) - min\n res = csapi.node_update(f'{cshost}/cs/cs.exe', nodeId, 0, 'test1', 'desc1', {}, {}, {})\n\n # move node and apply categories\n cats = { '1279234_2': 'test' }\n res = csapi.node_update(f'{cshost}/cs/cs.exe', nodeId, newDestId, '', '', {}, {}, cats)\n\n # delete a node\n res = csapi.node_delete(f'{cshost}/cs/cs.exe', nodeId)\n \n # download a document into file system\n res = csapi.node_download_file(f'{cshost}/otcs/cs.exe', nodeId, '', '/home/fitsch/Downloads', 'test-download.pdf')\n\n # download a document as base64 string\n res = csapi.node_download_bytes(f'{cshost}/otcs/cs.exe', nodeId, '')\n # {'message', 'file_size', 'base64' }\n\n # upload a document from file system\n res = csapi.node_upload_file(f'{cshost}/otcs/cs.exe', nodeId, '/home/fitsch/Downloads', 'test-download.pdf', 'test-upload.pdf', { '30724_2': '2020-03-17' })\n\n # upload a document from byte array\n barr = open('/home/fitsch/Downloads/test-download.pdf', 'rb').read()\n res = csapi.node_upload_bytes(f'{cshost}/otcs/cs.exe', nodeId, barr, 'test-upload.pdf', {'30724_2': '2020-03-17'})\n\n # covert a Content Server path to a Node ID\n res = csapi.path_to_id(f'{cshost}/otcs/cs.exe', 'Content Server Categories:SuccessFactors:OTHCM_WS_Employee_Categories:Personal Information')\n\n # get all volumes in Content Server\n res = csapi.volumes_get(f'{cshost}/otcs/cs.exe')\n # [\n # {\n # 'properties': \n # {\n # 'id': 2006, \n # 'name': 'Content Server Categories'\n # }\n # }, \n # {\n # 'properties': \n # {\n # 'id': 2000, \n # 'name': 'Enterprise'\n # }\n # }, \n # ...\n # ]\n\n```\n\n## Category Functions (Metadata)\n```python\n # get node information and load categories\n res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], True, False, False)\n\n # add category to node\n res = csapi.node_category_add(f'{cshost}/otcs/cs.exe', nodeId, { \"category_id\": 32133, \"32133_2\": \"8000\", \"32133_39\": [\"test 1\", \"test 2\"], \"32133_33_1_34\": \"Org Unit 1\", \"32133_33_1_35\": \"Org Unit Desc 1\", \"32133_33_2_34\": \"Org Unit 2\", \"32133_33_2_35\": \"Org Unit Desc 2\" } )\n\n # update category on a node\n res = csapi.node_category_update(f'{cshost}/otcs/cs.exe', nodeId, 32133, { \"32133_2\": \"8000\", \"32133_39\": [\"test 1\", \"test 2\"], \"32133_33_1_34\": \"Org Unit 1\", \"32133_33_1_35\": \"Org Unit Desc 1\", \"32133_33_2_34\": \"Org Unit 2\", \"32133_33_2_35\": \"Org Unit Desc 2\" } )\n \n # delete category from a node\n res = csapi.node_category_delete(f'{cshost}/otcs/cs.exe', nodeId, 32133)\n\n # read all category attributes - use i.e. path_to_id() to get cat_id\n res = csapi.category_get_mappings(f'{cshost}/otcs/cs.exe', cat_id)\n # {\n # 'main_name': 'Job Information', \n # 'main_id': 32133, \n # 'map_names': \n # {\n # 'Company Code': '32133_2', \n # 'Company Code Description': '32133_3', \n # ...\n # }, \n # 'map_ids': \n # {\n # '32133_2': 'Company Code', \n # '32133_3': 'Company Code Description', \n # ...\n # }\n # }\n \n # get category information for a specific attribute\n res = csapi.category_attribute_id_get(f'{cshost}/otcs/cs.exe', 'Content Server Categories:SuccessFactors:OTHCM_WS_Employee_Categories:Personal Information', 'User ID')\n # {\n # 'category_id': 30643, \n # 'category_name': 'Personal Information', \n # 'attribute_key': '30643_26', \n # 'attribute_name': 'User ID'\n # }\n```\n\n## Classification Functions\n```python\n # get node information and load classifications\n res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, False, True)\n\n # apply classifications to node\n res = csapi.node_classifications_apply(f'{cshost}/otcs/cs.exe', nodeId, False, [120571,120570])\n \n # same function to remove classification 120570 from node\n res = csapi.node_classifications_apply(f'{cshost}/otcs/cs.exe', nodeId, False, [120571])\n```\n\n## Permission Functions\n```python\n # get node information and load permissions\n res = csapi.node_get(f'{cshost}/otcs/cs.exe', nodeId, ['id', 'name'], False, True, False)\n\n # apply owner permissions on node\n res = csapi.node_permissions_owner_apply(f'{cshost}/otcs/cs.exe', nodeId, { \"permissions\":[\"delete\",\"delete_versions\",\"edit_attributes\",\"edit_permissions\",\"modify\",\"reserve\",\"see\",\"see_contents\"], \"right_id\": 1000 })\n\n # delete owner permission from node\n res = csapi.node_permissions_owner_delete(f'{cshost}/otcs/cs.exe', nodeId)\n\n # apply group permissions on node\n res = csapi.node_permissions_group_apply(f'{cshost}/otcs/cs.exe', nodeId, {\"permissions\":[\"delete\",\"delete_versions\",\"edit_attributes\",\"edit_permissions\",\"modify\",\"reserve\",\"see\",\"see_contents\"], \"right_id\": 2001 })\n\n # delete group permission from node\n res = csapi.node_permissions_group_delete(f'{cshost}/otcs/cs.exe', nodeId)\n\n # apply public permissions on node\n res = csapi.node_permissions_public_apply(f'{cshost}/otcs/cs.exe', nodeId, {\"permissions\":[\"delete\",\"delete_versions\",\"edit_attributes\",\"edit_permissions\",\"modify\",\"reserve\",\"see\",\"see_contents\"] })\n\n # delete public permission from node\n res = csapi.node_permissions_public_delete(f'{cshost}/otcs/cs.exe', nodeId)\n\n # apply a new custom permissions on node\n res = csapi.node_permissions_custom_apply(f'{cshost}/otcs/cs.exe', nodeId, [{\"permissions\":[\"see\",\"see_contents\"], \"right_id\": 1001 }])\n\n # update an existing custom permissions on node\n res = csapi.node_permissions_custom_update(f'{cshost}/otcs/cs.exe', nodeId, 2001, {\"permissions\":[\"delete\",\"delete_versions\",\"edit_attributes\",\"edit_permissions\",\"modify\",\"reserve\",\"see\",\"see_contents\"] })\n\n # delete a custom permissions from node\n res = csapi.node_permissions_custom_delete(f'{cshost}/otcs/cs.exe', nodeId, 1001)\n```\n\n## Smart Document Types Functions\n```python\n # get all smart document types\n res = csapi.smartdoctypes_get_all(f'{cshost}/otcs/cs.exe')\n for smartdoctype in res:\n print(f\"{smartdoctype['workspace_template_names']} - {smartdoctype['dataId']} - {smartdoctype['name']} --> {smartdoctype['classification_id']} - {smartdoctype['classification_name']}\")\n\n # get rules of a smart document type\n smartDocTypeId = smartdoctype['dataId']\n res = csapi.smartdoctypes_rules_get(f'{cshost}/otcs/cs.exe', smartDocTypeId)\n for smartdoctype in res:\n print(f\"{smartdoctype['template_name']} ({smartdoctype['template_id']}) - {smartdoctype['smartdocumenttype_id']} - RuleID: {smartdoctype['rule_id']} / DocGen: {smartdoctype['document_generation']} --> Classification: {smartdoctype['classification_id']} --> Location: {smartdoctype['location']}\")\n\n # get rule detail\n ruleId = smartdoctype['rule_id']\n res = csapi.smartdoctype_rule_detail_get(f'{cshost}/otcs/cs.exe', ruleId)\n for rule_tab in res:\n print(f\"tab: {rule_tab['bot_key']} - data: {rule_tab['data']}\")\n\n # create smart document type under \"Smart Document Types\" root folder 6004 (id is different per system) -> see get_volumes() function\n res = csapi.smartdoctype_add(f'{cshost}/otcs/cs.exe', 6004, categoryId, 'smart doc test')\n\n # add workspace template to rule\n res = csapi.smartdoctype_workspacetemplate_add(f'{cshost}/otcs/cs.exe', smartDocTypeId, classificationId, templateId)\n # {\n # 'is_othcm_template': True, \n # 'ok': True, \n # 'rule_id': 11, \n # 'statusCode': 200\n # }\n\n # add workspace template to rule -> get locationId with path_to_id() function\n location = csapi.path_to_id(f'{cshost}/otcs/cs.exe', 'Content Server Document Templates:SuccessFactors:Employee CHE:01 Entry Documents:110 Recruiting / Application')\n # {'id': 120603, 'name': '110 Recruiting / Application'}\n locationId = location.get('id', 0)\n res = csapi.smartdoctype_rule_context_save(f'{cshost}/otcs/cs.exe', ruleId, categoryId, locationId, 'update')\n # {\n # 'ok': True, \n # 'statusCode': 200, \n # 'updatedAttributeIds': [2], \n # 'updatedAttributeNames': ['Date of Origin']\n # }\n\n # add 'mandatory' tab in rule\n res = csapi.smartdoctype_rule_mandatory_save(f'{cshost}/otcs/cs.exe', ruleId, True, 'add')\n\n # update 'mandatory' tab in rule\n res = csapi.smartdoctype_rule_mandatory_save(f'{cshost}/otcs/cs.exe', ruleId, False, 'update')\n\n # delete 'mandatory' tab in rule\n res = csapi.smartdoctype_rule_mandatory_delete(f'{cshost}/otcs/cs.exe', ruleId)\n\n # add 'document expiration' tab in rule\n res = csapi.smartdoctype_rule_documentexpiration_save(f'{cshost}/otcs/cs.exe', ruleId, True, 2, 0, 6, 'add')\n\n # update 'document expiration' tab in rule\n res = csapi.smartdoctype_rule_documentexpiration_save(f'{cshost}/otcs/cs.exe', ruleId, False, 2, 0, 4, 'update')\n\n # delete 'document expiration' tab in rule\n res = csapi.smartdoctype_rule_documentexpiration_delete(f'{cshost}/otcs/cs.exe', ruleId)\n\n # add 'document generation' tab in rule\n res = csapi.smartdoctype_rule_generatedocument_save(f'{cshost}/otcs/cs.exe', ruleId, True, 'add')\n\n # update 'document generation' tab in rule\n res = csapi.smartdoctype_rule_generatedocument_save(f'{cshost}/otcs/cs.exe', ruleId, False, 'update')\n\n # delete 'document generation' tab in rule\n res = csapi.smartdoctype_rule_generatedocument_delete(f'{cshost}/otcs/cs.exe', ruleId)\n\n # add 'allow upload' tab in rule\n res = csapi.smartdoctype_rule_allowupload_save(f'{cshost}/otcs/cs.exe', ruleId, [2001], 'add')\n\n # update 'allow upload' tab in rule\n res = csapi.smartdoctype_rule_allowupload_save(f'{cshost}/otcs/cs.exe', ruleId, [2001,120593], 'update')\n\n # delete 'allow upload' tab in rule\n res = csapi.smartdoctype_rule_allowupload_delete(f'{cshost}/otcs/cs.exe', ruleId)\n\n # add 'upload approval' tab in rule\n res = csapi.smartdoctype_rule_uploadapproval_save(f'{cshost}/otcs/cs.exe', ruleId, True, workflowMapId, [{'wfrole': 'Approver', 'member': 2001 }], 'add')\n\n # update 'allow upload' tab in rule\n res = csapi.smartdoctype_rule_uploadapproval_save(f'{cshost}/otcs/cs.exe', ruleId, True, workflowMapId, [{'wfrole': 'Approver', 'member': 120593 }], 'update')\n\n # delete 'allow upload' tab in rule\n res = csapi.smartdoctype_rule_uploadapproval_delete(f'{cshost}/otcs/cs.exe', ruleId)\n```\n\n## Business Workspace Functions\n```python\n # get business workspace node id by business object type and business object id\n res = csapi.businessworkspace_search(f'{cshost}/otcs/cs.exe', 'SuccessFactors', 'sfsf:user', 'Z70080539', 1)\n\n # get customized smart document types for business workspace\n # bws_id from businessworkspace_search()\n res = csapi.businessworkspace_smartdoctypes_get(f'{cshost}/otcs/cs.exe', bws_id)\n # [{'classification_id': 120571, 'classification_name': 'Application Documents', 'classification_description': '', 'category_id': 6002, 'location': '122061:122063', 'document_generation': 0, 'required': 0, 'template_id': 120576}, ...]\n \n # get category definition for smart document type to be used for document upload into business workspace\n # bws_id from businessworkspace_search()\n # cat_id from businessworkspace_smartdoctypes_get()\n res = csapi.businessworkspace_categorydefinition_for_upload_get(f'{cshost}/otcs/cs.exe', bws_id, cat_id)\n\n # upload file using smart document type into business workspace\n res = csapi.businessworkspace_hr_upload_file(f'{cshost}/otcs/cs.exe', bws_id, '/home/fitsch/Downloads', 'test-download.pdf', 'application.pdf', class_dict['classification_id'], cat_id, cat_dict)\n \n ##### ########################## #####\n ##### snippet for upload process #####\n ##### ########################## #####\n res = csapi.businessworkspace_search(f'{cshost}/otcs/cs.exe', 'SuccessFactors', 'sfsf:user', 'Z70080539', 1)\n\n bws_id = -1\n class_name = 'Application Documents'\n class_dict = {}\n cat_id = -1\n cat_attr_date_of_origin = ''\n cat_dict = {}\n date_of_origin = datetime(2020, 5, 17)\n # res = {'results': [{'id': 122051, 'name': 'Employee Z70080539 Phil Egger', 'parent_id': 30648}, ... ], 'page_total': 1}\n if res and res.get('results', []) and len(res.get('results', [])) > 0:\n bws_id = res['results'][0].get('id', -1)\n\n if bws_id > 0:\n res = csapi.businessworkspace_smartdoctypes_get(f'{cshost}/otcs/cs.exe', bws_id)\n # res = [{'classification_id': 120571, 'classification_name': 'Application Documents', 'classification_description': '', 'category_id': 6002, 'location': '122061:122063', 'document_generation': 0, 'required': 0, 'template_id': 120576}, ... ]\n if res:\n for class_def in res:\n if class_def['classification_name'] == class_name:\n class_dict = class_def\n break\n\n if class_dict:\n # class_dict = {'classification_id': 120571, 'classification_name': 'Application Documents', 'classification_description': '', 'category_id': 6002, 'location': '122061:122063', 'document_generation': 0, 'required': 0, 'template_id': 120576}\n res = csapi.businessworkspace_categorydefinition_for_upload_get(f'{cshost}/otcs/cs.exe', bws_id, class_dict['category_id'])\n # res = [{'data': {'category_id': 6002, '6002_2': None}, 'options': {}, 'form': {}, 'schema': {'properties': {'category_id': {'readonly': False, 'required': False, 'title': 'Document Type Details', 'type': 'integer'}, '6002_2': {'readonly': False, 'required': False, 'title': 'Date of Origin', 'type': 'date'}}, 'type': 'object'}}]\n if res and len(res) > 0:\n if res[0].get('schema', {}) and res[0]['schema'].get('properties', {}):\n # res[0]['schema']['properties'] = {'category_id': {'readonly': False, 'required': False, 'title': 'Document Type Details', 'type': 'integer'}, '6002_2': {'readonly': False, 'required': False, 'title': 'Date of Origin', 'type': 'date'}}\n cat_id = class_dict['category_id']\n for p in res[0]['schema']['properties']:\n if str(cat_id) in p and res[0]['schema']['properties'][p].get('type', '') == 'date' and 'Origin' in res[0]['schema']['properties'][p].get('title', ''):\n cat_attr_date_of_origin = p\n break\n\n if cat_id > 0 and cat_attr_date_of_origin:\n cat_dict = { cat_attr_date_of_origin: date_of_origin.isoformat() }\n else:\n deflogger.info(f'Date Of Origin not found in Category {class_dict['category_id']} for Workspace {bws_id}')\n\n try:\n res = csapi.businessworkspace_hr_upload_file(f'{cshost}/otcs/cs.exe', bws_id, '/home/fitsch/Downloads', 'test-download.pdf', 'application.pdf', class_dict['classification_id'], cat_id, cat_dict)\n if res > 0:\n deflogger.info(f'File successfully uploaded - {res}')\n else:\n raise Exception(f'Invalid Node ID returned: {res}')\n except Exception as innerErr:\n deflogger.error(f'File failed to upload {innerErr}')\n else:\n deflogger.error(f'Classification Definition not found for {class_name} in Workspace {bws_id}')\n\n```\n\n## WebReport Functions\n```python\n # call web report by nickname using parameters\n res = csapi.webreport_nickname_call(f'{cshost}/otcs/cs.exe', 'WR_API_Test', {'p_name': 'name', 'p_desc': 'description'})\n\n # call web report by node id using parameters\n res = csapi.webreport_nodeid_call(f'{cshost}/otcs/cs.exe', wr_id, {'p_name': 'name', 'p_desc': 'description'})\n```\n\n## Server Information Functions\n```python\n # ping Content Server\n res = csapi.ping(f'{cshost}/otcs/cs.exe')\n\n # get server info (version, metadata languages, ...)\n res = csapi.server_info(f'{cshost}/otcs/cs.exe')\n print(f\"Version: {res['server']['version']}\")\n print('Metadata Languages:')\n for lang in res['server']['metadata_languages']:\n print(f\"{lang['language_code']} - {lang['display_name']}\")\n```\n\n## Basic API Functions - in case that something is not available in this class\n```python\n # GET API Call\n res = csapi.call_get(f'{cshost}/otcs/cs.exe/api/v1/nodes/2000/classifications')\n\n # POST API Call using form-url-encoded -> i.e. do fancy search\n res = csapi.call_post_form_url_encoded(f'{cshost}/otcs/cs.exe/api/v2/search', { 'body': json.dumps({ 'where': 'OTName: \"Personal Information\" and OTSubType: 131 and OTLocation: 2006' })})\n\n # POST API Call using form-data -> can be used to upload files\n data = { 'type': 144, 'parent_id': parent_id, 'name': remote_filename }\n params = { 'body' : json.dumps(data) }\n files = {'file': (remote_filename, open(os.path.join(local_folder, local_filename), 'rb'), 'application/octet-stream')}\n res = csapi.call_post_form_data(f'{cshost}/otcs/cs.exe/api/v2/nodes', params, files)\n\n # PUT API Call\n params = {'body': json.dumps(category)}\n res = self.call_put(f'{cshost}/otcs/cs.exe/api/v2/nodes/{node_id}/categories/{category_id}', params)\n\n # DELETE API Call\n res = self.call_delete(f'{cshost}/otcs/cs.exe/api/v2/nodes/{node_id}/categories/{category_id}')\n```\n\n# Disclaimer\n\nCopyright \u00a9 2025 by Philipp Egger, All Rights Reserved. The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n",
"bugtrack_url": null,
"license": null,
"summary": "Simple Python library to call Opentext Extended ECM REST API",
"version": "25.1.0",
"project_urls": {
"Homepage": "https://github.com/fitschgo/xecm",
"Issues": "https://github.com/fitschgo/xecm/issues"
},
"split_keywords": [
"xecm",
" opentext",
" extendedecm",
" contentserver",
" otcs",
" otds"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "a87fe594156bd06a346838c0f8675fa619f1a4bea2bad3636b08e74ec2510d11",
"md5": "ad06b7642cf890bfb6b7d0e8bc3f52ec",
"sha256": "0bd7ffd6c451b8af4c4cb7b5b0e3181afe4602f623d3a5c87b422287b1e84924"
},
"downloads": -1,
"filename": "xecm-25.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ad06b7642cf890bfb6b7d0e8bc3f52ec",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 22884,
"upload_time": "2025-07-09T08:51:59",
"upload_time_iso_8601": "2025-07-09T08:51:59.011337Z",
"url": "https://files.pythonhosted.org/packages/a8/7f/e594156bd06a346838c0f8675fa619f1a4bea2bad3636b08e74ec2510d11/xecm-25.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "ea27aad3e0403ad18da38f4eab919da18680cc978a6d3bac0017c6232d4514cb",
"md5": "351e000a14167397a1fa20d4a222c25a",
"sha256": "02fb599c2ad790f637ee6f1c44f8aaec42d79a2d542af22928215bafa655f9c7"
},
"downloads": -1,
"filename": "xecm-25.1.0.tar.gz",
"has_sig": false,
"md5_digest": "351e000a14167397a1fa20d4a222c25a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 27784,
"upload_time": "2025-07-09T08:52:00",
"upload_time_iso_8601": "2025-07-09T08:52:00.565292Z",
"url": "https://files.pythonhosted.org/packages/ea/27/aad3e0403ad18da38f4eab919da18680cc978a6d3bac0017c6232d4514cb/xecm-25.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-09 08:52:00",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "fitschgo",
"github_project": "xecm",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "xecm"
}