Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tree-wide: add support for ABAP DDIC Data Element CRUD #120

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
163 changes: 163 additions & 0 deletions sap/adt/dataelement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
"""ABAP Data Element ADT functionality module"""

from enum import Enum

from sap.adt.objects import ADTObject, ADTObjectType, ADTObjectSourceEditor
# pylint: disable=unused-import
from sap.adt.annotations import (
OrderedClassMembers,
xml_text_node_property,
XmlNodeProperty,
)
from sap.adt.objects import XMLNamespace, XMLNS_ADTCORE

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

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


class DataElementValidationIssues(Enum):
""""DataElement validation issues"""

NO_ISSUE = 0 # DataElement is correctly defined
MISSING_DOMAIN_NAME = 1 # DataElement is Domain based by Domain Name was not provided
MISSING_TYPE_NAME = 2 # DataElement is created from a predefined type by it was not provided


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

typ = xml_text_node_property('dtel:typeKind')
typ_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')


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
)

definition = XmlNodeProperty('dtel:dataElement')

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

self._metadata.package_reference.name = package
self.definition = DataElementDefinition()

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

self.definition.typ = value

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

self.definition.typ_name = value.upper() if value is not None else None

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

self.definition.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.definition.data_type_length = value

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

self.definition.data_type_decimals = value

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

self.definition.label_short = value

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

self.definition.label_medium = value

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

self.definition.label_long = value

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

self.definition.label_heading = value

def normalize(self):
"""Validate Data Element setup before save"""
de = self.definition

if de.typ == 'domain':
de.data_type = ''
de.data_type_length = '0'
de.data_type_decimals = '0'
if de.typ == 'predefinedAbapType':
de.typ_name = ''

de.label_short_length = de.label_short_length if not de.label_short_length else LABELS_LENGTH['short']
de.label_medium_length = de.label_medium_length if not de.label_medium_length else LABELS_LENGTH['medium']
de.label_long_length = de.label_long_length if not de.label_long_length else LABELS_LENGTH['long']
de.label_heading_length = de.label_heading_length if not de.label_heading_length else LABELS_LENGTH['heading']

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

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

if self.definition.typ == 'domain' and not self.definition.typ_name:
return DataElementValidationIssues.MISSING_DOMAIN_NAME
if self.definition.typ == 'predefinedAbapType' and not self.definition.data_type:
return DataElementValidationIssues.MISSING_TYPE_NAME

return DataElementValidationIssues.NO_ISSUE
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 ''
buca92 marked this conversation as resolved.
Show resolved Hide resolved
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
131 changes: 131 additions & 0 deletions sap/cli/dataelement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""ADT proxy for ABAP DDIC Data Element"""

from sap.errors import SAPCliError
import sap.adt
from sap.adt.dataelement import DataElementValidationIssues
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()
# pylint: disable=too-many-branches
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()

validation_issue_key = dataelement.validate()

match validation_issue_key:
case DataElementValidationIssues.MISSING_DOMAIN_NAME:
console.printerr(
'Domain name must be provided (--domain_name) if the type (--type) is "domain"')

case DataElementValidationIssues.MISSING_TYPE_NAME:
console.printerr(
'Data type name must be provided (--data_type) if the type (--type) is "predefinedAbapType"')

case DataElementValidationIssues.NO_ISSUE:
pass

case _:
raise SAPCliError(
f'BUG: please report a forgotten case DataElementValidationIssues({validation_issue_key})')

if validation_issue_key != DataElementValidationIssues.NO_ISSUE:
return 1

# 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
Loading