Skip to content

Commit

Permalink
tree-wide: add support for ABAP DDIC Data Element CRUD
Browse files Browse the repository at this point in the history
  • Loading branch information
Libor Bucek committed Sep 19, 2023
1 parent b251600 commit ecbf301
Show file tree
Hide file tree
Showing 10 changed files with 1,052 additions and 4 deletions.
7 changes: 4 additions & 3 deletions doc/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
18. [flp](commands/flp.md) - Fiori Launchpad
19. [rap](commands/businessservice.md) - RAP Business Services
20. [strust](commands/strust.md) - SSL Certificates
21. [structure](commands/structure.md) - ABAP DDIC structures
22. [table](commands/table.md) - ABAP DDIC transparent tables
23. [badi](commands/badi.md) - New style (Enhancements) BAdI operations
21. [dataelement](commands/dataelement.md) - ABAP DDIC Data Elements
22. [structure](commands/structure.md) - ABAP DDIC structures
23. [table](commands/table.md) - ABAP DDIC transparent tables
24. [badi](commands/badi.md) - New style (Enhancements) BAdI operations
26 changes: 26 additions & 0 deletions doc/commands/dataelement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Data Element

- [Data Element](#data-element)
- [define](#define)

## define

Define an ABAP DDIC Data Element.

```bash
sapcli dataelement define DATA_ELEMENT_NAME --type=domain|predefinedAbapType [--corrnr TRANSPORT] [--activate] [--no-error-existing] [--domain_name] [--data_type] [--data_type_length] [--data_type_decimals] [--label_short] [--label_medium] [--label_long] [--label_heading]
```

* _DATA\_ELEMENT\_NAME_ specifying the name of the data element
* _--type [domain|predefinedAbapType]_ type kind
* _--domain\_name_ domain name (e.g. BUKRS) [default = ''] - mandatory in case the _--type_=domain **(optional)**
* _--data\_type_ data type (e.g. CHAR) [default = ''] - mandatory in case the _--type_=predefinedAbapType **(optional)**
* _--data\_type\_length_ data type length (e.g. 5) [default = '0'] **(optional)**
* _--data\_type\_decimals_ data type decimals (e.g. 3) [default = '0'] **(optional)**
* _--label\_short_ short label [default = ''] **(optional)**
* _--label\_medium_ medium label [default = ''] **(optional)**
* _--label\_long_ long label [default = ''] **(optional)**
* _--label\_heading_ heading label [default = ''] **(optional)**
* _--corrnr TRANSPORT_ specifies CTS Transport Request Number **(optional)**
* _--activate_ activate after finishing the data element modification **(optional)**
* _--no-error-existing_ do not fail if data element already exists **(optional)**
1 change: 1 addition & 0 deletions sap/adt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
from sap.adt.table import Table # noqa: F401
from sap.adt.enhancement_implementation import EnhancementImplementation # noqa: F401
from sap.adt.structure import Structure # noqa: F401
from sap.adt.dataelement import DataElement # noqa: F401
185 changes: 185 additions & 0 deletions sap/adt/dataelement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""ABAP Data Element ADT functionality module"""

from sap.adt.objects import ADTObject, ADTObjectType, ADTCoreData, ADTObjectSourceEditor
# pylint: disable=unused-import
from sap.adt.annotations import OrderedClassMembers, xml_element, xml_text_node_property
from sap.adt.objects import XMLNamespace, XMLNS_ADTCORE
from sap.errors import SAPCliError

XMLNS_DTEL = XMLNamespace('dtel', 'http://www.sap.com/adt/dictionary/dataelements')

LABELS_LENGTH = {
'short': '10',
'medium': '20',
'long': '40',
'heading': '55'
}


class ADTDataElementData(ADTCoreData):
"""Data Element nodes data.
"""

# pylint: disable=too-many-instance-attributes
# pylint: disable=too-few-public-methods
class DataElement(metaclass=OrderedClassMembers):
"""ADT Data Element data collector"""

type = xml_text_node_property('dtel:typeKind')
type_name = xml_text_node_property('dtel:typeName')
data_type = xml_text_node_property('dtel:dataType')
data_type_length = xml_text_node_property('dtel:dataTypeLength')
data_type_decimals = xml_text_node_property('dtel:dataTypeDecimals')
label_short = xml_text_node_property('dtel:shortFieldLabel')
label_short_length = xml_text_node_property('dtel:shortFieldLength')
label_short_max_length = xml_text_node_property('dtel:shortFieldMaxLength')
label_medium = xml_text_node_property('dtel:mediumFieldLabel')
label_medium_length = xml_text_node_property('dtel:mediumFieldLength')
label_medium_max_length = xml_text_node_property('dtel:mediumFieldMaxLength')
label_long = xml_text_node_property('dtel:longFieldLabel')
label_long_length = xml_text_node_property('dtel:longFieldLength')
label_long_max_length = xml_text_node_property('dtel:longFieldMaxLength')
label_heading = xml_text_node_property('dtel:headingFieldLabel')
label_heading_length = xml_text_node_property('dtel:headingFieldLength')
label_heading_max_length = xml_text_node_property('dtel:headingFieldMaxLength')
search_help = xml_text_node_property('dtel:searchHelp')
search_help_parameter = xml_text_node_property('dtel:searchHelpParameter')
set_get_parameter = xml_text_node_property('dtel:setGetParameter')
default_component_name = xml_text_node_property('dtel:defaultComponentName')
deactivate_input_history = xml_text_node_property('dtel:deactivateInputHistory')
change_document = xml_text_node_property('dtel:changeDocument')
left_to_right_direction = xml_text_node_property('dtel:leftToRightDirection')
deactivate_bidi_filtering = xml_text_node_property('dtel:deactivateBIDIFiltering')

# pylint: disable=too-many-arguments
def __init__(self, package=None, description=None, language=None,
master_language=None, master_system=None, responsible=None,
package_reference=None, abap_language_version=None):
super().__init__(package, description, language,
master_language, master_system, responsible,
package_reference, abap_language_version)

self._data_element = ADTDataElementData.DataElement()

@property
def data_element(self):
"""The Data Element's reference"""

return self._data_element


class DataElement(ADTObject):
"""ABAP Data Element"""

OBJTYPE = ADTObjectType(
'DTEL/DE',
'ddic/dataelements',
XMLNamespace('blue', 'http://www.sap.com/wbobj/dictionary/dtel', parents=[XMLNS_ADTCORE, XMLNS_DTEL]),
'application/vnd.sap.adt.dataelements.v2+xml',
{
'application/vnd.sap.adt.dataelements.v2+xml': '',
'application/vnd.sap.adt.dataelements.v1+xml': ''
},
'wbobj',
editor_factory=ADTObjectSourceEditor
)

def __init__(self, connection, name, package=None, metadata=None):
super().__init__(connection, name, metadata, active_status='inactive')

self._metadata = ADTDataElementData(
metadata.package, metadata.description,
metadata.language, metadata.master_language,
metadata.master_system, metadata.responsible,
metadata.package_reference.name if metadata.package_reference is not None else None,
metadata.abap_language_version
) if metadata is not None else ADTDataElementData()

self._metadata.package_reference.name = package

@xml_element('dtel:dataElement')
def data_element(self):
"""The Data Element's reference"""

return self._metadata.data_element

def set_type(self, value):
"""Setter for Type Kind element"""

self._metadata.data_element.type = value

def set_type_name(self, value):
"""Setter for Type Name element"""

self._metadata.data_element.type_name = value.upper() if value is not None else None

def set_data_type(self, value):
"""Setter for Data Type element"""

self._metadata.data_element.data_type = value.upper() if value is not None else None

def set_data_type_length(self, value):
"""Setter for Data Type Length element"""

self._metadata.data_element.data_type_length = value

def set_data_type_decimals(self, value):
"""Setter for Data Type Decimals element"""

self._metadata.data_element.data_type_decimals = value

def set_label_short(self, value):
"""Setter for Label Short element"""

self._metadata.data_element.label_short = value

def set_label_medium(self, value):
"""Setter for Label Medium element"""

self._metadata.data_element.label_medium = value

def set_label_long(self, value):
"""Setter for Label Long element"""

self._metadata.data_element.label_long = value

def set_label_heading(self, value):
"""Setter for Label Heading element"""

self._metadata.data_element.label_heading = value

def normalize(self):
"""Validate Data Element setup before save"""
if self._metadata.data_element.type == 'domain':
self._metadata.data_element.data_type = ''
self._metadata.data_element.data_type_length = '0'
self._metadata.data_element.data_type_decimals = '0'
if self._metadata.data_element.type == 'predefinedAbapType':
self._metadata.data_element.type_name = ''

# pylint: disable=line-too-long
self._metadata.data_element.label_short_length = self._metadata.data_element.label_short_length if not self._metadata.data_element.label_short_length else LABELS_LENGTH['short']
# pylint: disable=line-too-long
self._metadata.data_element.label_medium_length = self._metadata.data_element.label_medium_length if not self._metadata.data_element.label_medium_length else LABELS_LENGTH['medium']
# pylint: disable=line-too-long
self._metadata.data_element.label_long_length = self._metadata.data_element.label_long_length if not self._metadata.data_element.label_long_length else LABELS_LENGTH['long']
# pylint: disable=line-too-long
self._metadata.data_element.label_heading_length = self._metadata.data_element.label_heading_length if not self._metadata.data_element.label_heading_length else LABELS_LENGTH['heading']

# pylint: disable=line-too-long
self._metadata.data_element.deactivate_input_history = self._metadata.data_element.deactivate_input_history if self._metadata.data_element.deactivate_input_history is not None else False
# pylint: disable=line-too-long
self._metadata.data_element.change_document = self._metadata.data_element.change_document if self._metadata.data_element.change_document is not None else False
# pylint: disable=line-too-long
self._metadata.data_element.left_to_right_direction = self._metadata.data_element.left_to_right_direction if self._metadata.data_element.left_to_right_direction is not None else False
# pylint: disable=line-too-long
self._metadata.data_element.deactivate_bidi_filtering = self._metadata.data_element.deactivate_bidi_filtering if self._metadata.data_element.deactivate_bidi_filtering is not None else False

def validate(self):
"""Validate Data Element setup"""

if self._metadata.data_element.type == 'domain' and not self._metadata.data_element.type_name:
raise SAPCliError('Domain name must be provided (--domain_name) if the type (--type) is "domain"')
if self._metadata.data_element.type == 'predefinedAbapType' and not self._metadata.data_element.data_type:
# pylint: disable=line-too-long
raise SAPCliError('Data type name must be provided (--data_type) if the type (--type) is "predefinedAbapType"')
4 changes: 3 additions & 1 deletion sap/adt/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ def get_uri_for_type(self, mimetype):
"""

try:
return '/' + self._typeuris[mimetype]
mimetype_uri = self._typeuris[mimetype]

return '/' + mimetype_uri if mimetype_uri != '' else ''
except KeyError:
# pylint: disable=raise-missing-from
raise SAPCliError('Object {type} does not support plain \'text\' format')
Expand Down
2 changes: 2 additions & 0 deletions sap/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def commands():
import sap.cli.table
import sap.cli.badi
import sap.cli.structure
import sap.cli.dataelement

if CommandsCache.adt is None:
CommandsCache.adt = [
Expand All @@ -69,6 +70,7 @@ def commands():
(adt_connection_from_args, sap.cli.rap.CommandGroup()),
(adt_connection_from_args, sap.cli.table.CommandGroup()),
(adt_connection_from_args, sap.cli.structure.CommandGroup()),
(adt_connection_from_args, sap.cli.dataelement.CommandGroup()),
(adt_connection_from_args, sap.cli.checkin.CommandGroup()),
(adt_connection_from_args, sap.cli.badi.CommandGroup()),
]
Expand Down
109 changes: 109 additions & 0 deletions sap/cli/dataelement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""ADT proxy for ABAP DDIC Data Element"""

import sap.adt
from sap.adt.errors import ExceptionResourceAlreadyExists
import sap.cli.object
import sap.cli.wb
import sap.cli.core


class CommandGroup(sap.cli.object.CommandGroupObjectMaster):
"""Adapter converting command line parameters to sap.adt.DataElement methods
calls.
"""

def __init__(self):
super().__init__('dataelement')

self.define()

def instance(self, connection, name, args, metadata=None):
package = None
if hasattr(args, 'package'):
package = args.package

return sap.adt.DataElement(connection, name.upper(), package=package, metadata=metadata)


@CommandGroup.argument_corrnr()
@CommandGroup.argument('--no-error-existing', action='store_true', default=False,
help='Do not fail if data element already exists')
@CommandGroup.argument('-a', '--activate', action='store_true', default=False, help='Activate after modification')
@CommandGroup.argument('-t', '--type', required=True, choices=['domain', 'predefinedAbapType'],
type=str, help='Type kind')
@CommandGroup.argument('-d', '--domain_name', default=None, type=str, help='Domain name')
@CommandGroup.argument('-dt', '--data_type', default=None, type=str, help='Data type')
@CommandGroup.argument('-dtl', '--data_type_length', default='0', type=str, help='Data type length')
@CommandGroup.argument('-dtd', '--data_type_decimals', default='0', type=str, help='Data type decimals')
@CommandGroup.argument('-ls', '--label_short', default='', type=str, help='Short label')
@CommandGroup.argument('-lm', '--label_medium', default='', type=str, help='Medium label')
@CommandGroup.argument('-ll', '--label_long', default='', type=str, help='Long label')
@CommandGroup.argument('-lh', '--label_heading', default='', type=str, help='Heading label')
@CommandGroup.argument('package', help='Package assignment')
@CommandGroup.argument('description', help='Data element description')
@CommandGroup.argument('name', help='Data element name')
@CommandGroup.command()
def define(connection, args):
"""Changes attributes of the given Data Element"""

console = sap.cli.core.get_console()

metadata = sap.adt.ADTCoreData(language='EN', master_language='EN', responsible=connection.user,
description=args.description)

dataelement = sap.adt.DataElement(connection, args.name.upper(), args.package, metadata=metadata)

# Create Data Element
console.printout(f'Creating data element {args.name}')
try:
dataelement.create(args.corrnr)
except ExceptionResourceAlreadyExists as error:
# Date Element already exists
console.printout(f'Data element {args.name} already exists')
if not args.no_error_existing:
raise error

# Fetch data element's content
dataelement.fetch()

if hasattr(args, 'type'):
dataelement.set_type(args.type)

if hasattr(args, 'domain_name'):
dataelement.set_type_name(args.domain_name)

if hasattr(args, 'data_type'):
dataelement.set_data_type(args.data_type)

if hasattr(args, 'data_type_length'):
dataelement.set_data_type_length(args.data_type_length)

if hasattr(args, 'data_type_decimals'):
dataelement.set_data_type_decimals(args.data_type_decimals)

if hasattr(args, 'label_short'):
dataelement.set_label_short(args.label_short)

if hasattr(args, 'label_medium'):
dataelement.set_label_medium(args.label_medium)

if hasattr(args, 'label_long'):
dataelement.set_label_long(args.label_long)

if hasattr(args, 'label_heading'):
dataelement.set_label_heading(args.label_heading)

dataelement.normalize()

dataelement.validate()

# Push Data Element changes
console.printout(f'Data element {args.name} setup performed')
with dataelement.open_editor(corrnr=args.corrnr) as editor:
editor.push()

# Activate Data Element
if args.activate:
console.printout(f'Data element {args.name} activation performed')
activator = sap.cli.wb.ObjectActivationWorker()
sap.cli.object.activate_object_list(activator, ((args.name, dataelement),), count=1)
Loading

0 comments on commit ecbf301

Please sign in to comment.