abap-adt-py 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- abap_adt_py-0.1.0/LICENSE +21 -0
- abap_adt_py-0.1.0/PKG-INFO +38 -0
- abap_adt_py-0.1.0/README.md +0 -0
- abap_adt_py-0.1.0/pyproject.toml +31 -0
- abap_adt_py-0.1.0/setup.cfg +4 -0
- abap_adt_py-0.1.0/src/abap_adt_py/__init__.py +0 -0
- abap_adt_py-0.1.0/src/abap_adt_py/adt_client.py +131 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/__init__.py +0 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/activate.py +33 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/content.py +52 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/create.py +181 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/delete.py +17 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/lock.py +38 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/login.py +20 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/prettyprint.py +93 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/search.py +18 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/unittest.py +101 -0
- abap_adt_py-0.1.0/src/abap_adt_py/api/xml_namespaces.py +8 -0
- abap_adt_py-0.1.0/src/abap_adt_py/compat_typing.py +14 -0
- abap_adt_py-0.1.0/src/abap_adt_py/http_request.py +46 -0
- abap_adt_py-0.1.0/src/abap_adt_py/response_parsing.py +43 -0
- abap_adt_py-0.1.0/src/abap_adt_py.egg-info/PKG-INFO +38 -0
- abap_adt_py-0.1.0/src/abap_adt_py.egg-info/SOURCES.txt +24 -0
- abap_adt_py-0.1.0/src/abap_adt_py.egg-info/dependency_links.txt +1 -0
- abap_adt_py-0.1.0/src/abap_adt_py.egg-info/requires.txt +2 -0
- abap_adt_py-0.1.0/src/abap_adt_py.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 timkoehne
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abap-adt-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for interacting with SAP systems using the ADT (ABAP Development Tools) protocol.
|
|
5
|
+
Author-email: Tim Köhne <20153452+timkoehne@users.noreply.github.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2023 timkoehne
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
Project-URL: Homepage, https://github.com/timkoehne/abap-adt-py
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/timkoehne/abap-adt-py/issues
|
|
29
|
+
Classifier: Programming Language :: Python :: 3
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Requires-Python: >=3.7
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Requires-Dist: requests>=2.25
|
|
37
|
+
Requires-Dist: typing-extensions>=4.7.1
|
|
38
|
+
Dynamic: license-file
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "abap-adt-py"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Python library for interacting with SAP systems using the ADT (ABAP Development Tools) protocol."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Tim Köhne", email = "20153452+timkoehne@users.noreply.github.com" }
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.7"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"requests>=2.25",
|
|
17
|
+
"typing-extensions>=4.7.1",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.7",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
"Homepage" = "https://github.com/timkoehne/abap-adt-py"
|
|
28
|
+
"Bug Tracker" = "https://github.com/timkoehne/abap-adt-py/issues"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from requests.auth import HTTPBasicAuth
|
|
3
|
+
|
|
4
|
+
from .compat_typing import Literal, List, Dict
|
|
5
|
+
from .api.prettyprint import PrettyPrintSettings, prettyprint, set_pretty_printer_settings
|
|
6
|
+
from .api.create import create, create_test_class_include
|
|
7
|
+
from .api.activate import activate
|
|
8
|
+
from .api.create import ObjectTypes
|
|
9
|
+
from .api.delete import delete
|
|
10
|
+
from .api.lock import lock, unlock
|
|
11
|
+
from .api.login import login
|
|
12
|
+
from .api.content import get_object_source, set_object_source
|
|
13
|
+
from .api.search import search_object
|
|
14
|
+
from .api.unittest import UnitTestAlert, UnittestFlags, run_unit_test
|
|
15
|
+
from .http_request import HttpRequestParameters
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AdtClient:
|
|
19
|
+
sap_host: str
|
|
20
|
+
csrf_token: str = "fetch"
|
|
21
|
+
request_number: int = 0
|
|
22
|
+
statefulness: Literal["stateless", "stateful"] = "stateless"
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, sap_host: str, username: str, password: str, client: str, language: str
|
|
26
|
+
):
|
|
27
|
+
self.username = username
|
|
28
|
+
self.session = requests.Session()
|
|
29
|
+
self.session.auth = HTTPBasicAuth(username, password)
|
|
30
|
+
self.sap_host = sap_host
|
|
31
|
+
self.client = client
|
|
32
|
+
self.language = language
|
|
33
|
+
|
|
34
|
+
def build_request_parameters(self) -> HttpRequestParameters:
|
|
35
|
+
http_request_parameters: HttpRequestParameters = {
|
|
36
|
+
"host": self.sap_host,
|
|
37
|
+
"csrf_token": self.csrf_token,
|
|
38
|
+
"statefulness": self.statefulness,
|
|
39
|
+
"request_number": self.request_number,
|
|
40
|
+
"session": self.session,
|
|
41
|
+
}
|
|
42
|
+
self.request_number += 1
|
|
43
|
+
return http_request_parameters
|
|
44
|
+
|
|
45
|
+
def login(self) -> bool:
|
|
46
|
+
http_request_parameters = self.build_request_parameters()
|
|
47
|
+
csrf_token = login(http_request_parameters)
|
|
48
|
+
if csrf_token:
|
|
49
|
+
self.csrf_token = csrf_token
|
|
50
|
+
return True
|
|
51
|
+
else:
|
|
52
|
+
raise Exception("Login failed.")
|
|
53
|
+
|
|
54
|
+
def search_object(self, query: str, max_results: int = 1) -> List[Dict[str, str]]:
|
|
55
|
+
http_request_parameters = self.build_request_parameters()
|
|
56
|
+
elements = search_object(http_request_parameters, query, max_results)
|
|
57
|
+
return elements
|
|
58
|
+
|
|
59
|
+
def get_object_source(
|
|
60
|
+
self, object_uri: str, version: Literal["active", "inactive"] = "active"
|
|
61
|
+
) -> str:
|
|
62
|
+
http_request_parameters = self.build_request_parameters()
|
|
63
|
+
response = get_object_source(http_request_parameters, object_uri, version)
|
|
64
|
+
return response
|
|
65
|
+
|
|
66
|
+
def activate(self, object_name: str, object_uri: str) -> bool:
|
|
67
|
+
http_request_parameters = self.build_request_parameters()
|
|
68
|
+
response = activate(http_request_parameters, object_name, object_uri)
|
|
69
|
+
return response
|
|
70
|
+
|
|
71
|
+
def lock(self, object_uri: str) -> str:
|
|
72
|
+
self.statefulness = "stateful"
|
|
73
|
+
http_request_parameters = self.build_request_parameters()
|
|
74
|
+
response = lock(http_request_parameters, object_uri)
|
|
75
|
+
return response
|
|
76
|
+
|
|
77
|
+
def unlock(self, object_uri: str, lock_handle: str) -> bool:
|
|
78
|
+
http_request_parameters = self.build_request_parameters()
|
|
79
|
+
response = unlock(http_request_parameters, object_uri, lock_handle)
|
|
80
|
+
self.statefulness = "stateless"
|
|
81
|
+
return response
|
|
82
|
+
|
|
83
|
+
def set_object_source(
|
|
84
|
+
self, object_uri: str, source_code: str, lock_handle: str
|
|
85
|
+
) -> bool:
|
|
86
|
+
http_request_parameters = self.build_request_parameters()
|
|
87
|
+
response = set_object_source(
|
|
88
|
+
http_request_parameters, object_uri, source_code, lock_handle
|
|
89
|
+
)
|
|
90
|
+
return response
|
|
91
|
+
|
|
92
|
+
def run_unit_test(
|
|
93
|
+
self, object_uri: str, unit_test_flags: UnittestFlags = UnittestFlags()
|
|
94
|
+
) -> List[UnitTestAlert]:
|
|
95
|
+
http_request_parameters = self.build_request_parameters()
|
|
96
|
+
response = run_unit_test(http_request_parameters, object_uri, unit_test_flags)
|
|
97
|
+
return response
|
|
98
|
+
|
|
99
|
+
def delete(self, object_uri: str, lock_handle: str) -> bool:
|
|
100
|
+
http_request_parameters = self.build_request_parameters()
|
|
101
|
+
response = delete(http_request_parameters, object_uri, lock_handle)
|
|
102
|
+
return response
|
|
103
|
+
|
|
104
|
+
def create(
|
|
105
|
+
self, object_type: ObjectTypes, name: str, parent: str, description: str
|
|
106
|
+
) -> bool:
|
|
107
|
+
http_request_parameters = self.build_request_parameters()
|
|
108
|
+
response = create(
|
|
109
|
+
http_request_parameters,
|
|
110
|
+
object_type,
|
|
111
|
+
name,
|
|
112
|
+
parent,
|
|
113
|
+
description,
|
|
114
|
+
self.username,
|
|
115
|
+
)
|
|
116
|
+
return response
|
|
117
|
+
|
|
118
|
+
def create_test_class_include(self, class_name: str, lock_handle: str) -> bool:
|
|
119
|
+
http_request_parameters = self.build_request_parameters()
|
|
120
|
+
response = create_test_class_include(http_request_parameters, class_name, lock_handle)
|
|
121
|
+
return response
|
|
122
|
+
|
|
123
|
+
def prettyprint(self, src: str) -> str:
|
|
124
|
+
http_request_parameters = self.build_request_parameters()
|
|
125
|
+
response = prettyprint(http_request_parameters, src)
|
|
126
|
+
return response
|
|
127
|
+
|
|
128
|
+
def prettyprint_settings(self, settings: PrettyPrintSettings) -> bool:
|
|
129
|
+
http_request_parameters = self.build_request_parameters()
|
|
130
|
+
response = set_pretty_printer_settings(http_request_parameters, settings)
|
|
131
|
+
return response
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from ..http_request import HttpRequestParameters, request
|
|
2
|
+
from ..response_parsing import find_xml_element_attributes, find_xml_elements_attributes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def activate(
|
|
6
|
+
http_request_parameters: HttpRequestParameters, object_name: str, object_uri: str
|
|
7
|
+
) -> bool:
|
|
8
|
+
|
|
9
|
+
body = f"""
|
|
10
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
11
|
+
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
12
|
+
<adtcore:objectReference adtcore:uri="{object_uri}" adtcore:name="{object_name}"/>
|
|
13
|
+
</adtcore:objectReferences>
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
response = request(
|
|
17
|
+
http_request_parameters,
|
|
18
|
+
uri="/sap/bc/adt/activation",
|
|
19
|
+
params={"method": "activate", "preauditRequested": "true"},
|
|
20
|
+
body=body,
|
|
21
|
+
method="POST",
|
|
22
|
+
content_type="application/xml",
|
|
23
|
+
)
|
|
24
|
+
print(response.text)
|
|
25
|
+
properties = find_xml_element_attributes(response.text, "chkl:properties")
|
|
26
|
+
if (
|
|
27
|
+
properties["activationExecuted"] == "true"
|
|
28
|
+
or properties["generationExecuted"] == "true"
|
|
29
|
+
):
|
|
30
|
+
return True
|
|
31
|
+
else:
|
|
32
|
+
msg_elements = find_xml_elements_attributes(response.text, "msg")
|
|
33
|
+
raise Exception(f"{response.status_code} - Activation failed.\n{msg_elements}")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from ..compat_typing import Literal
|
|
2
|
+
from ..http_request import HttpRequestParameters, request
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_object_source(
|
|
6
|
+
http_request_parameters: HttpRequestParameters,
|
|
7
|
+
object_uri: str,
|
|
8
|
+
version: Literal["active", "inactive"] = "active",
|
|
9
|
+
) -> str:
|
|
10
|
+
|
|
11
|
+
params = {}
|
|
12
|
+
if version:
|
|
13
|
+
params["version"] = version
|
|
14
|
+
|
|
15
|
+
response = request(
|
|
16
|
+
http_request_parameters=http_request_parameters,
|
|
17
|
+
uri=object_uri,
|
|
18
|
+
method="GET",
|
|
19
|
+
body="",
|
|
20
|
+
params=params,
|
|
21
|
+
content_type="application/xml",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if response.status_code == 200:
|
|
25
|
+
return response.text
|
|
26
|
+
else:
|
|
27
|
+
raise Exception(
|
|
28
|
+
f"{response.status_code} - Failed to get object source.\n{response.text}"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def set_object_source(
|
|
33
|
+
http_request_parameters: HttpRequestParameters,
|
|
34
|
+
object_uri: str,
|
|
35
|
+
source_code: str,
|
|
36
|
+
lock_handle: str,
|
|
37
|
+
) -> bool:
|
|
38
|
+
response = request(
|
|
39
|
+
http_request_parameters=http_request_parameters,
|
|
40
|
+
uri=object_uri,
|
|
41
|
+
method="PUT",
|
|
42
|
+
body=source_code,
|
|
43
|
+
params={"lockHandle": lock_handle},
|
|
44
|
+
content_type="text/plain; charset=utf-8",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if response.status_code == 200:
|
|
48
|
+
return True
|
|
49
|
+
else:
|
|
50
|
+
raise Exception(
|
|
51
|
+
f"{response.status_code} - Failed to set source code for {object_uri}\n{response.text}"
|
|
52
|
+
)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from ..compat_typing import Literal, TypeAlias, Dict, TypedDict
|
|
2
|
+
from ..http_request import HttpRequestParameters, request
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CreateableTypeDetails(TypedDict):
|
|
6
|
+
path: str
|
|
7
|
+
xml_name: str
|
|
8
|
+
xml_namespace: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
ObjectTypes: TypeAlias = Literal[
|
|
12
|
+
"PROG/P",
|
|
13
|
+
"CLAS/OC",
|
|
14
|
+
"TABL/DT",
|
|
15
|
+
"INTF/OI",
|
|
16
|
+
"PROG/I",
|
|
17
|
+
"FUGR/F",
|
|
18
|
+
"FUGR/FF",
|
|
19
|
+
"MSAG/N",
|
|
20
|
+
"DCLS/DL",
|
|
21
|
+
"DDLS/DF",
|
|
22
|
+
"DDLX/EX",
|
|
23
|
+
"DTEL/DE",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
CreateableTypes: TypeAlias = Dict[ObjectTypes, CreateableTypeDetails]
|
|
27
|
+
CREATEABLE_TYPES: CreateableTypes = {
|
|
28
|
+
"PROG/P": {
|
|
29
|
+
"path": "/sap/bc/adt/programs/programs",
|
|
30
|
+
"xml_name": "program:abapProgram",
|
|
31
|
+
"xml_namespace": 'xmlns:program="http://www.sap.com/adt/programs/programs"',
|
|
32
|
+
},
|
|
33
|
+
"CLAS/OC": {
|
|
34
|
+
"path": "/sap/bc/adt/oo/classes",
|
|
35
|
+
"xml_name": "class:abapClass",
|
|
36
|
+
"xml_namespace": 'xmlns:class="http://www.sap.com/adt/oo/classes"',
|
|
37
|
+
},
|
|
38
|
+
"TABL/DT": {
|
|
39
|
+
"path": "/sap/bc/adt/ddic/tables",
|
|
40
|
+
"xml_name": "blue:blueSource",
|
|
41
|
+
"xml_namespace": 'xmlns:blue="http://www.sap.com/wbobj/blue"',
|
|
42
|
+
},
|
|
43
|
+
"INTF/OI": {
|
|
44
|
+
"path": "/sap/bc/adt/oo/interfaces",
|
|
45
|
+
"xml_name": "intf:abapInterface",
|
|
46
|
+
"xml_namespace": 'xmlns:intf="http://www.sap.com/adt/oo/interfaces"',
|
|
47
|
+
},
|
|
48
|
+
"PROG/I": {
|
|
49
|
+
"path": "/sap/bc/adt/programs/includes",
|
|
50
|
+
"xml_name": "include:abapInclude",
|
|
51
|
+
"xml_namespace": 'xmlns:include="http://www.sap.com/adt/programs/includes"',
|
|
52
|
+
},
|
|
53
|
+
"FUGR/F": {
|
|
54
|
+
"path": "/sap/bc/adt/functions/groups",
|
|
55
|
+
"xml_name": "group:abapFunctionGroup",
|
|
56
|
+
"xml_namespace": 'xmlns:group="http://www.sap.com/adt/functions/groups"',
|
|
57
|
+
},
|
|
58
|
+
"FUGR/FF": {
|
|
59
|
+
"path": "/sap/bc/adt/functions/groups/{}/fmodules",
|
|
60
|
+
"xml_name": "fmodule:abapFunctionModule",
|
|
61
|
+
"xml_namespace": 'xmlns:fmodule="http://www.sap.com/adt/functions/fmodules"',
|
|
62
|
+
},
|
|
63
|
+
"MSAG/N": {
|
|
64
|
+
"path": "/sap/bc/adt/messageclass",
|
|
65
|
+
"xml_name": "mc:messageClass",
|
|
66
|
+
"xml_namespace": 'xmlns:mc="http://www.sap.com/adt/MessageClass"',
|
|
67
|
+
},
|
|
68
|
+
"DCLS/DL": {
|
|
69
|
+
"path": "/sap/bc/adt/acm/dcl/sources",
|
|
70
|
+
"xml_name": "dcl:dclSource",
|
|
71
|
+
"xml_namespace": 'xmlns:dcl="http://www.sap.com/adt/acm/dclsources"',
|
|
72
|
+
},
|
|
73
|
+
"DDLS/DF": {
|
|
74
|
+
"path": "/sap/bc/adt/ddic/ddl/sources",
|
|
75
|
+
"xml_name": "ddl:ddlSource",
|
|
76
|
+
"xml_namespace": 'xmlns:ddl="http://www.sap.com/adt/ddic/ddlsources"',
|
|
77
|
+
},
|
|
78
|
+
"DDLX/EX": {
|
|
79
|
+
"path": "/sap/bc/adt/ddic/ddlx/sources",
|
|
80
|
+
"xml_name": "ddlx:ddlxSource",
|
|
81
|
+
"xml_namespace": 'xmlns:ddlx="http://www.sap.com/adt/ddic/ddlxsources"',
|
|
82
|
+
},
|
|
83
|
+
"DTEL/DE": {
|
|
84
|
+
"path": "/sap/bc/adt/ddic/dataelements",
|
|
85
|
+
"xml_name": "blue:wbobj",
|
|
86
|
+
"xml_namespace": 'xmlns:blue="http://www.sap.com/wbobj/dictionary/dtel"',
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _build_body(
|
|
92
|
+
description: str, name: str, package: str, owner: str, object_type: ObjectTypes
|
|
93
|
+
) -> str:
|
|
94
|
+
|
|
95
|
+
if object_type in ["FUGR/FF"]:
|
|
96
|
+
parent_ref = f"""
|
|
97
|
+
<adtcore:containerRef adtcore:name="{owner}"
|
|
98
|
+
adtcore:type="FUGR/F"
|
|
99
|
+
adtcore:uri="{package}" />"""
|
|
100
|
+
else:
|
|
101
|
+
parent_ref = f"""
|
|
102
|
+
<adtcore:packageRef adtcore:name="{package}"/>
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
creatable_object = CREATEABLE_TYPES[object_type]
|
|
106
|
+
|
|
107
|
+
return f"""
|
|
108
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
109
|
+
<{creatable_object["xml_name"]} {creatable_object["xml_namespace"]}
|
|
110
|
+
xmlns:adtcore="http://www.sap.com/adt/core"
|
|
111
|
+
adtcore:description="{description}"
|
|
112
|
+
adtcore:name="{name}" adtcore:type="{object_type}"
|
|
113
|
+
adtcore:responsible="{owner}" >
|
|
114
|
+
{parent_ref}
|
|
115
|
+
</{creatable_object["xml_name"]}>
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def create(
|
|
120
|
+
http_request_parameters: HttpRequestParameters,
|
|
121
|
+
object_type: ObjectTypes,
|
|
122
|
+
name: str,
|
|
123
|
+
parent: str,
|
|
124
|
+
description: str,
|
|
125
|
+
owner: str,
|
|
126
|
+
) -> bool:
|
|
127
|
+
|
|
128
|
+
object_uri = CREATEABLE_TYPES[object_type]["path"]
|
|
129
|
+
|
|
130
|
+
# noop for most objects but eg. function modules need their function group in their object uri
|
|
131
|
+
object_uri = object_uri.format(parent)
|
|
132
|
+
|
|
133
|
+
body = _build_body(
|
|
134
|
+
description=description,
|
|
135
|
+
name=name,
|
|
136
|
+
package=parent,
|
|
137
|
+
object_type=object_type,
|
|
138
|
+
owner=owner,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
response = request(
|
|
142
|
+
http_request_parameters=http_request_parameters,
|
|
143
|
+
uri=object_uri,
|
|
144
|
+
method="POST",
|
|
145
|
+
body=body,
|
|
146
|
+
params={},
|
|
147
|
+
content_type="application/*",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if 200 <= response.status_code <= 300:
|
|
151
|
+
return True
|
|
152
|
+
else:
|
|
153
|
+
raise Exception(
|
|
154
|
+
f"{response.status_code} - Failed to create object {name}\n{response.text}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def create_test_class_include(
|
|
159
|
+
http_request_parameters: HttpRequestParameters, class_name: str, lock_handle: str
|
|
160
|
+
) -> bool:
|
|
161
|
+
|
|
162
|
+
body = f"""
|
|
163
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
164
|
+
<class:abapClassInclude xmlns:class="http://www.sap.com/adt/oo/classes"
|
|
165
|
+
xmlns:adtcore="http://www.sap.com/adt/core" adtcore:name="{class_name}" class:includeType="testclasses"/>
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
response = request(
|
|
169
|
+
http_request_parameters=http_request_parameters,
|
|
170
|
+
uri=f"/sap/bc/adt/oo/classes/{class_name}/includes",
|
|
171
|
+
method="POST",
|
|
172
|
+
body=body,
|
|
173
|
+
params={"lockHandle": lock_handle},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if 200 <= response.status_code <= 300:
|
|
177
|
+
return True
|
|
178
|
+
else:
|
|
179
|
+
raise Exception(
|
|
180
|
+
f"{response.status_code} - Failed to create testclass for {class_name}\n{response.text}"
|
|
181
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..http_request import HttpRequestParameters, request
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def delete(http_request_parameters: HttpRequestParameters, object_uri: str, lock_handle: str) -> bool:
|
|
5
|
+
response = request(
|
|
6
|
+
http_request_parameters=http_request_parameters,
|
|
7
|
+
uri=object_uri,
|
|
8
|
+
body="",
|
|
9
|
+
params={"lockHandle": lock_handle},
|
|
10
|
+
method="DELETE",
|
|
11
|
+
)
|
|
12
|
+
if response.status_code == 200:
|
|
13
|
+
return True
|
|
14
|
+
else:
|
|
15
|
+
raise Exception(
|
|
16
|
+
f"{response.status_code} Failed to delete {object_uri}.\n{response.text}"
|
|
17
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from ..http_request import HttpRequestParameters, request
|
|
2
|
+
from ..response_parsing import find_xml_element_text
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def lock(http_request_parameters: HttpRequestParameters, object_uri: str) -> str:
|
|
6
|
+
response = request(
|
|
7
|
+
http_request_parameters=http_request_parameters,
|
|
8
|
+
uri=object_uri,
|
|
9
|
+
body="",
|
|
10
|
+
params={"_action": "LOCK", "accessMode": "MODIFY"},
|
|
11
|
+
method="POST",
|
|
12
|
+
)
|
|
13
|
+
if response.status_code == 200:
|
|
14
|
+
lock_handle = find_xml_element_text(response.text, ".//LOCK_HANDLE")
|
|
15
|
+
return lock_handle
|
|
16
|
+
else:
|
|
17
|
+
raise Exception(
|
|
18
|
+
f"{response.status_code} Failed to lock {object_uri}.\n{response.text}"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def unlock(
|
|
23
|
+
http_request_parameters: HttpRequestParameters, object_uri: str, lock_handle: str
|
|
24
|
+
) -> bool:
|
|
25
|
+
response = request(
|
|
26
|
+
http_request_parameters=http_request_parameters,
|
|
27
|
+
uri=object_uri,
|
|
28
|
+
body="",
|
|
29
|
+
params={"_action": "UNLOCK", "lockHandle": lock_handle},
|
|
30
|
+
method="POST",
|
|
31
|
+
content_type="plain/text; charset=utf-8",
|
|
32
|
+
)
|
|
33
|
+
if response.status_code == 200:
|
|
34
|
+
return True
|
|
35
|
+
else:
|
|
36
|
+
raise Exception(
|
|
37
|
+
f"{response.status_code} Failed to unlock {object_uri}\n{response.text}"
|
|
38
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from ..http_request import HttpRequestParameters, request
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def login(http_request_parameters: HttpRequestParameters) -> str:
|
|
5
|
+
|
|
6
|
+
response = request(
|
|
7
|
+
http_request_parameters=http_request_parameters,
|
|
8
|
+
uri="/sap/bc/adt/compatibility/graph",
|
|
9
|
+
method="GET",
|
|
10
|
+
body="",
|
|
11
|
+
params={},
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if response.status_code == 200:
|
|
15
|
+
csrf_token = response.headers.get("x-csrf-token")
|
|
16
|
+
if csrf_token is None:
|
|
17
|
+
raise Exception("CSRF token not found in response headers.")
|
|
18
|
+
return csrf_token
|
|
19
|
+
else:
|
|
20
|
+
raise Exception(f"{response.status_code} - Login failed.\n{response.text}")
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from ..compat_typing import Literal, NotRequired, TypedDict
|
|
2
|
+
from ..http_request import HttpRequestParameters, request
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PrettyPrintSettings(TypedDict):
|
|
6
|
+
indentation: bool
|
|
7
|
+
style: Literal[
|
|
8
|
+
"toLower", "toUpper", "keywordLower", "keywordUpper", "none", "keywordAuto"
|
|
9
|
+
]
|
|
10
|
+
condense_method_calls: NotRequired[bool]
|
|
11
|
+
default_auto_style: NotRequired[Literal["keywordLower", "keywordUpper"]]
|
|
12
|
+
format_method_calls: NotRequired[bool]
|
|
13
|
+
keep_identifier: NotRequired[bool]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def set_pretty_printer_settings(
|
|
17
|
+
http_request_parameters: HttpRequestParameters, settings: PrettyPrintSettings
|
|
18
|
+
):
|
|
19
|
+
|
|
20
|
+
condense_method_calls = (
|
|
21
|
+
f"prettyprintersettings:condenseMethodCalls=\"{str(settings['condense_method_calls']).lower()}\""
|
|
22
|
+
if "condense_method_calls" in settings
|
|
23
|
+
else ""
|
|
24
|
+
)
|
|
25
|
+
default_auto_style = (
|
|
26
|
+
f"prettyprintersettings:defaultAutoStyle=\"{settings['default_auto_style']}\""
|
|
27
|
+
if "default_auto_style" in settings
|
|
28
|
+
else ""
|
|
29
|
+
)
|
|
30
|
+
format_method_calls = (
|
|
31
|
+
f"prettyprintersettings:formatMethodCalls=\"{str(settings['format_method_calls']).lower()}\""
|
|
32
|
+
if "format_method_calls" in settings
|
|
33
|
+
else ""
|
|
34
|
+
)
|
|
35
|
+
indentation = (
|
|
36
|
+
f"prettyprintersettings:indentation=\"{str(settings['indentation']).lower()}\""
|
|
37
|
+
if "indentation" in settings
|
|
38
|
+
else ""
|
|
39
|
+
)
|
|
40
|
+
keep_identifier = (
|
|
41
|
+
f"prettyprintersettings:keepIdentifier=\"{str(settings['keep_identifier']).lower()}\""
|
|
42
|
+
if "keep_identifier" in settings
|
|
43
|
+
else ""
|
|
44
|
+
)
|
|
45
|
+
style = (
|
|
46
|
+
f"prettyprintersettings:style=\"{settings['style']}\""
|
|
47
|
+
if "style" in settings
|
|
48
|
+
else ""
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
body = f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
52
|
+
<prettyprintersettings:PrettyPrinterSettings
|
|
53
|
+
xmlns:prettyprintersettings="http://www.sap.com/adt/prettyprintersettings"
|
|
54
|
+
{condense_method_calls}
|
|
55
|
+
{default_auto_style}
|
|
56
|
+
{format_method_calls}
|
|
57
|
+
{indentation}
|
|
58
|
+
{keep_identifier}
|
|
59
|
+
{style}
|
|
60
|
+
/>
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
response = request(
|
|
64
|
+
http_request_parameters,
|
|
65
|
+
uri="/sap/bc/adt/abapsource/prettyprinter/settings",
|
|
66
|
+
params={},
|
|
67
|
+
body=body,
|
|
68
|
+
method="PUT",
|
|
69
|
+
content_type="application/xml",
|
|
70
|
+
)
|
|
71
|
+
if response.status_code == 200:
|
|
72
|
+
return True
|
|
73
|
+
else:
|
|
74
|
+
raise Exception(
|
|
75
|
+
f"{response.status_code} Failed to set prettyprint settings\n{response.text}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def prettyprint(http_request_parameters: HttpRequestParameters, src: str):
|
|
80
|
+
response = request(
|
|
81
|
+
http_request_parameters,
|
|
82
|
+
uri="/sap/bc/adt/abapsource/prettyprinter",
|
|
83
|
+
params={},
|
|
84
|
+
body=src,
|
|
85
|
+
method="POST",
|
|
86
|
+
content_type="text/plain; charset=utf-8",
|
|
87
|
+
)
|
|
88
|
+
if response.status_code == 200:
|
|
89
|
+
return response.text
|
|
90
|
+
else:
|
|
91
|
+
raise Exception(
|
|
92
|
+
f"{response.status_code} Failed to prettyprint\n{response.text}"
|
|
93
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from ..http_request import HttpRequestParameters, request
|
|
2
|
+
from ..response_parsing import find_xml_elements_attributes
|
|
3
|
+
from ..compat_typing import List, Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def search_object(
|
|
7
|
+
http_request_parameters: HttpRequestParameters, query: str, max_results: int = 1
|
|
8
|
+
) -> List[Dict[str, str]]:
|
|
9
|
+
|
|
10
|
+
response = request(
|
|
11
|
+
http_request_parameters=http_request_parameters,
|
|
12
|
+
uri="/sap/bc/adt/repository/informationsystem/search",
|
|
13
|
+
method="GET",
|
|
14
|
+
body="",
|
|
15
|
+
params={"operation": "quickSearch", "query": query, "maxResults": max_results},
|
|
16
|
+
)
|
|
17
|
+
elements = find_xml_elements_attributes(response.text, "adtcore:objectReference")
|
|
18
|
+
return elements
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import xml.etree.ElementTree as et
|
|
2
|
+
|
|
3
|
+
from ..compat_typing import List, TypedDict
|
|
4
|
+
from ..api.xml_namespaces import XML_NAMESPACES
|
|
5
|
+
from ..http_request import HttpRequestParameters, request
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UnittestFlags:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
harmless: bool = True,
|
|
12
|
+
dangerous: bool = False,
|
|
13
|
+
critical: bool = False,
|
|
14
|
+
short: bool = True,
|
|
15
|
+
medium: bool = False,
|
|
16
|
+
long: bool = False,
|
|
17
|
+
):
|
|
18
|
+
self.harmless = harmless
|
|
19
|
+
self.dangerous = dangerous
|
|
20
|
+
self.critical = critical
|
|
21
|
+
self.short = short
|
|
22
|
+
self.medium = medium
|
|
23
|
+
self.long = long
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UnitTestAlert(TypedDict):
|
|
27
|
+
title: str
|
|
28
|
+
kind: str
|
|
29
|
+
severity: str
|
|
30
|
+
details: List[str]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _parse_alerts(xml_text: str) -> List[UnitTestAlert]:
|
|
34
|
+
root = et.fromstring(xml_text)
|
|
35
|
+
errors = []
|
|
36
|
+
alerts = root.findall(".//alert", XML_NAMESPACES)
|
|
37
|
+
for alert in alerts:
|
|
38
|
+
a = {}
|
|
39
|
+
title = alert.find(".//title", XML_NAMESPACES)
|
|
40
|
+
a["title"] = title.text if title is not None else ""
|
|
41
|
+
a["kind"] = alert.attrib.get("kind", "")
|
|
42
|
+
a["severity"] = alert.attrib.get("severity", "")
|
|
43
|
+
stack = alert.find(".//stack", XML_NAMESPACES)
|
|
44
|
+
a["stack"] = stack if stack is not None else []
|
|
45
|
+
details = []
|
|
46
|
+
for detail in alert.findall("./details/detail", XML_NAMESPACES):
|
|
47
|
+
detail_str = "\n".join(
|
|
48
|
+
x.attrib["text"]
|
|
49
|
+
for x in detail.iter()
|
|
50
|
+
if "text" in x.attrib is not None
|
|
51
|
+
)
|
|
52
|
+
details.append(detail_str)
|
|
53
|
+
a["details"] = details
|
|
54
|
+
errors.append(a)
|
|
55
|
+
return errors
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run_unit_test(
|
|
59
|
+
http_request_parameters: HttpRequestParameters,
|
|
60
|
+
object_uri: str,
|
|
61
|
+
unit_test_flags: UnittestFlags = UnittestFlags(),
|
|
62
|
+
):
|
|
63
|
+
body = f"""
|
|
64
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
65
|
+
<aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit">
|
|
66
|
+
<external>
|
|
67
|
+
<coverage active="false"/>
|
|
68
|
+
</external>
|
|
69
|
+
<options>
|
|
70
|
+
<uriType value="semantic"/>
|
|
71
|
+
<testDeterminationStrategy sameProgram="true" assignedTests="false"/>
|
|
72
|
+
<testRiskLevels harmless="{str(unit_test_flags.harmless).lower()}" dangerous="{str(unit_test_flags.dangerous).lower()}" critical="{str(unit_test_flags.critical).lower()}"/>
|
|
73
|
+
<testDurations short="{str(unit_test_flags.short).lower()}" medium="{str(unit_test_flags.medium).lower()}" long="{str(unit_test_flags.long).lower()}"/>
|
|
74
|
+
<withNavigationUri enabled="true"/>
|
|
75
|
+
</options>
|
|
76
|
+
<adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core">
|
|
77
|
+
<objectSet kind="inclusive">
|
|
78
|
+
<adtcore:objectReferences>
|
|
79
|
+
<adtcore:objectReference adtcore:uri="{object_uri}"/>
|
|
80
|
+
</adtcore:objectReferences>
|
|
81
|
+
</objectSet>
|
|
82
|
+
</adtcore:objectSets>
|
|
83
|
+
</aunit:runConfiguration>
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
response = request(
|
|
87
|
+
http_request_parameters=http_request_parameters,
|
|
88
|
+
uri="/sap/bc/adt/abapunit/testruns",
|
|
89
|
+
method="POST",
|
|
90
|
+
body=body,
|
|
91
|
+
params={},
|
|
92
|
+
content_type="application/xml",
|
|
93
|
+
)
|
|
94
|
+
if response.status_code == 200:
|
|
95
|
+
alerts = _parse_alerts(response.text)
|
|
96
|
+
return alerts
|
|
97
|
+
|
|
98
|
+
else:
|
|
99
|
+
raise Exception(
|
|
100
|
+
f"{response.status_code} - Failed to run unit test for {object_uri}\n{response.text}"
|
|
101
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
XML_NAMESPACES = {
|
|
2
|
+
"chkl": "http://www.sap.com/abapxml/checklist",
|
|
3
|
+
"atom": "http://www.w3.org/2005/Atom",
|
|
4
|
+
"adtcore": "http://www.sap.com/adt/core",
|
|
5
|
+
"exc": "http://www.sap.com/abapxml/types/communicationframework",
|
|
6
|
+
"asx": "http://www.sap.com/abapxml",
|
|
7
|
+
"aunit": "http://www.sap.com/adt/aunit",
|
|
8
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from typing import Literal, NotRequired, TypeAlias, Optional, Dict, List, TypedDict # Python 3.11+
|
|
3
|
+
except ImportError:
|
|
4
|
+
try:
|
|
5
|
+
from typing import Literal # Python 3.8 - 3.10
|
|
6
|
+
except ImportError:
|
|
7
|
+
from typing_extensions import Literal # Python 3.7 fallback
|
|
8
|
+
|
|
9
|
+
from typing_extensions import NotRequired
|
|
10
|
+
from typing_extensions import TypeAlias
|
|
11
|
+
from typing_extensions import Optional
|
|
12
|
+
from typing_extensions import Dict
|
|
13
|
+
from typing_extensions import List
|
|
14
|
+
from typing_extensions import TypedDict
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from .compat_typing import Literal, TypedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HttpRequestParameters(TypedDict):
|
|
6
|
+
host: str
|
|
7
|
+
csrf_token: str
|
|
8
|
+
statefulness: Literal["stateless", "stateful"]
|
|
9
|
+
request_number: int
|
|
10
|
+
session: requests.Session
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def request(
|
|
14
|
+
http_request_parameters: HttpRequestParameters,
|
|
15
|
+
uri: str,
|
|
16
|
+
method: Literal["GET", "POST", "PUT", "DELETE"],
|
|
17
|
+
body: str,
|
|
18
|
+
params: dict,
|
|
19
|
+
content_type: str = "application/xml",
|
|
20
|
+
) -> requests.Response:
|
|
21
|
+
|
|
22
|
+
config = {
|
|
23
|
+
"params": params,
|
|
24
|
+
"headers": {
|
|
25
|
+
"Accept": "*/*",
|
|
26
|
+
"Cache-Control": "no-cache",
|
|
27
|
+
"x-csrf-token": http_request_parameters["csrf_token"],
|
|
28
|
+
"X-sap-adt-sessiontype": http_request_parameters["statefulness"],
|
|
29
|
+
"content-type": content_type,
|
|
30
|
+
},
|
|
31
|
+
"url": http_request_parameters["host"] + uri,
|
|
32
|
+
"data": body,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
session = http_request_parameters["session"]
|
|
36
|
+
if method == "POST":
|
|
37
|
+
response = session.post(**config)
|
|
38
|
+
elif method == "GET":
|
|
39
|
+
response = session.get(**config)
|
|
40
|
+
elif method == "PUT":
|
|
41
|
+
response = session.put(**config)
|
|
42
|
+
elif method == "DELETE":
|
|
43
|
+
response = session.delete(**config)
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError(f"Unsupported method: {method}")
|
|
46
|
+
return response
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from xml.etree import ElementTree as et
|
|
2
|
+
from .compat_typing import Optional, Dict, List
|
|
3
|
+
from .api.xml_namespaces import XML_NAMESPACES
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _strip_namespace(name: str) -> str:
|
|
7
|
+
if name.startswith("{"):
|
|
8
|
+
return name.split("}", 1)[1]
|
|
9
|
+
if ":" in name:
|
|
10
|
+
return name.split(":", 1)[1]
|
|
11
|
+
return name
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _et_to_attributes_dict(element: Optional[et.Element]) -> Dict[str, str]:
|
|
15
|
+
cleaned = (
|
|
16
|
+
{_strip_namespace(key): value for key, value in element.attrib.items()}
|
|
17
|
+
if element is not None
|
|
18
|
+
else {}
|
|
19
|
+
)
|
|
20
|
+
return cleaned
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_xml_elements_attributes(xml_text: str, tag_name: str) -> List[Dict[str, str]]:
|
|
24
|
+
root = et.fromstring(xml_text)
|
|
25
|
+
elements = root.findall(tag_name, XML_NAMESPACES)
|
|
26
|
+
processed_elements = []
|
|
27
|
+
for element in elements:
|
|
28
|
+
cleaned = _et_to_attributes_dict(element)
|
|
29
|
+
processed_elements.append(cleaned)
|
|
30
|
+
return processed_elements
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_xml_element_attributes(xml_text: str, tag_name: str) -> Dict[str, str]:
|
|
34
|
+
root = et.fromstring(xml_text)
|
|
35
|
+
element = root.find(tag_name, XML_NAMESPACES)
|
|
36
|
+
element = _et_to_attributes_dict(element)
|
|
37
|
+
return element
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_xml_element_text(xml_text: str, tag_name: str):
|
|
41
|
+
root = et.fromstring(xml_text)
|
|
42
|
+
el = root.find(tag_name, XML_NAMESPACES)
|
|
43
|
+
return "".join(el.itertext()).strip() if el is not None else ""
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abap-adt-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for interacting with SAP systems using the ADT (ABAP Development Tools) protocol.
|
|
5
|
+
Author-email: Tim Köhne <20153452+timkoehne@users.noreply.github.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2023 timkoehne
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
Project-URL: Homepage, https://github.com/timkoehne/abap-adt-py
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/timkoehne/abap-adt-py/issues
|
|
29
|
+
Classifier: Programming Language :: Python :: 3
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Requires-Python: >=3.7
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Requires-Dist: requests>=2.25
|
|
37
|
+
Requires-Dist: typing-extensions>=4.7.1
|
|
38
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/abap_adt_py/__init__.py
|
|
5
|
+
src/abap_adt_py/adt_client.py
|
|
6
|
+
src/abap_adt_py/compat_typing.py
|
|
7
|
+
src/abap_adt_py/http_request.py
|
|
8
|
+
src/abap_adt_py/response_parsing.py
|
|
9
|
+
src/abap_adt_py.egg-info/PKG-INFO
|
|
10
|
+
src/abap_adt_py.egg-info/SOURCES.txt
|
|
11
|
+
src/abap_adt_py.egg-info/dependency_links.txt
|
|
12
|
+
src/abap_adt_py.egg-info/requires.txt
|
|
13
|
+
src/abap_adt_py.egg-info/top_level.txt
|
|
14
|
+
src/abap_adt_py/api/__init__.py
|
|
15
|
+
src/abap_adt_py/api/activate.py
|
|
16
|
+
src/abap_adt_py/api/content.py
|
|
17
|
+
src/abap_adt_py/api/create.py
|
|
18
|
+
src/abap_adt_py/api/delete.py
|
|
19
|
+
src/abap_adt_py/api/lock.py
|
|
20
|
+
src/abap_adt_py/api/login.py
|
|
21
|
+
src/abap_adt_py/api/prettyprint.py
|
|
22
|
+
src/abap_adt_py/api/search.py
|
|
23
|
+
src/abap_adt_py/api/unittest.py
|
|
24
|
+
src/abap_adt_py/api/xml_namespaces.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
abap_adt_py
|