hac-client-core 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.
- hac_client_core-0.1.0/LICENSE +21 -0
- hac_client_core-0.1.0/PKG-INFO +311 -0
- hac_client_core-0.1.0/README.md +276 -0
- hac_client_core-0.1.0/pyproject.toml +79 -0
- hac_client_core-0.1.0/setup.cfg +4 -0
- hac_client_core-0.1.0/src/hac_client_core/__init__.py +63 -0
- hac_client_core-0.1.0/src/hac_client_core/auth.py +83 -0
- hac_client_core-0.1.0/src/hac_client_core/client.py +855 -0
- hac_client_core-0.1.0/src/hac_client_core/models.py +268 -0
- hac_client_core-0.1.0/src/hac_client_core/py.typed +0 -0
- hac_client_core-0.1.0/src/hac_client_core/session.py +234 -0
- hac_client_core-0.1.0/src/hac_client_core.egg-info/PKG-INFO +311 -0
- hac_client_core-0.1.0/src/hac_client_core.egg-info/SOURCES.txt +14 -0
- hac_client_core-0.1.0/src/hac_client_core.egg-info/dependency_links.txt +1 -0
- hac_client_core-0.1.0/src/hac_client_core.egg-info/requires.txt +12 -0
- hac_client_core-0.1.0/src/hac_client_core.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SapCommerceTools
|
|
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,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hac-client-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client library for the SAP Commerce HAC (Hybris Administration Console) HTTP API
|
|
5
|
+
Author: SapCommerceTools
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SapCommerceTools/ha-client-core
|
|
8
|
+
Project-URL: Documentation, https://sapcommercetools.github.io/hac-client-core/
|
|
9
|
+
Project-URL: Repository, https://github.com/SapCommerceTools/ha-client-core
|
|
10
|
+
Project-URL: Issues, https://github.com/SapCommerceTools/ha-client-core/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/SapCommerceTools/ha-client-core/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: sap,commerce,hybris,hac,administration,groovy,flexiblesearch,impex
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: requests>=2.31.0
|
|
25
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
29
|
+
Requires-Dist: responses>=0.23.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
32
|
+
Provides-Extra: docs
|
|
33
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# hac-client-core
|
|
37
|
+
|
|
38
|
+
Python client library for the **SAP Commerce HAC** (Hybris Administration Console) HTTP API.
|
|
39
|
+
|
|
40
|
+
Execute Groovy scripts, run FlexibleSearch queries, import Impex data, and trigger system updates — all from Python, with automatic session management and CSRF handling.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Groovy script execution** — run arbitrary Groovy in commit or rollback mode
|
|
45
|
+
- **FlexibleSearch queries** — execute queries with typed result objects
|
|
46
|
+
- **Impex import** — import Impex content with configurable validation
|
|
47
|
+
- **System update** — trigger updates, select patches/parameters, poll logs
|
|
48
|
+
- **Session management** — automatic login, CSRF tokens, session caching across runs
|
|
49
|
+
- **Pluggable authentication** — ships with Basic Auth; extend `AuthHandler` for OAuth, JWT, API keys, etc.
|
|
50
|
+
- **Fully typed** — complete type annotations with a `py.typed` marker (PEP 561)
|
|
51
|
+
|
|
52
|
+
## Requirements
|
|
53
|
+
|
|
54
|
+
- Python ≥ 3.12
|
|
55
|
+
- A running SAP Commerce instance with HAC enabled
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Install from PyPI (once published):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install hac-client-core
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or install directly from the repository:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install git+https://github.com/SapCommerceTools/ha-client-core.git
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For development:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/SapCommerceTools/ha-client-core.git
|
|
75
|
+
cd hac-client-core
|
|
76
|
+
pip install -e ".[dev]"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from hac_client_core import HacClient, BasicAuthHandler
|
|
83
|
+
|
|
84
|
+
# 1. Create an auth handler
|
|
85
|
+
auth = BasicAuthHandler("admin", "nimda")
|
|
86
|
+
|
|
87
|
+
# 2. Create the client
|
|
88
|
+
client = HacClient(
|
|
89
|
+
base_url="https://localhost:9002",
|
|
90
|
+
auth_handler=auth,
|
|
91
|
+
ignore_ssl=True, # skip certificate verification (dev only)
|
|
92
|
+
session_persistence=True, # cache sessions between runs
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# 3. Login (or let it happen automatically on the first call)
|
|
96
|
+
client.login()
|
|
97
|
+
|
|
98
|
+
# 4. Execute a Groovy script
|
|
99
|
+
result = client.execute_groovy("return 'Hello from HAC'")
|
|
100
|
+
print(result.execution_result) # "Hello from HAC"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Usage
|
|
104
|
+
|
|
105
|
+
### Groovy script execution
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
result = client.execute_groovy(
|
|
109
|
+
script="return de.hybris.platform.core.Registry.applicationContext",
|
|
110
|
+
commit=False, # rollback mode (default)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if result.success:
|
|
114
|
+
print(result.output_text) # stdout output
|
|
115
|
+
print(result.execution_result) # return value
|
|
116
|
+
else:
|
|
117
|
+
print(result.stacktrace_text) # error details
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Set `commit=True` to execute in commit mode — changes will be persisted to the database.
|
|
121
|
+
|
|
122
|
+
### FlexibleSearch queries
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
result = client.execute_flexiblesearch(
|
|
126
|
+
query="SELECT {pk}, {code} FROM {Product} WHERE {code} LIKE '%camera%'",
|
|
127
|
+
max_count=50,
|
|
128
|
+
locale="en",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if result.success:
|
|
132
|
+
print(f"Columns: {result.headers}")
|
|
133
|
+
print(f"Rows returned: {result.result_count}")
|
|
134
|
+
for row in result.rows:
|
|
135
|
+
print(row)
|
|
136
|
+
else:
|
|
137
|
+
print(f"Query error: {result.exception}")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Impex import
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
impex = """
|
|
144
|
+
INSERT_UPDATE Product; code[unique=true]; name[lang=en]
|
|
145
|
+
; testProduct001 ; Test Product
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
result = client.import_impex(impex, validation_mode="import_strict")
|
|
149
|
+
|
|
150
|
+
if result.success:
|
|
151
|
+
print("Import completed")
|
|
152
|
+
else:
|
|
153
|
+
print(f"Import failed: {result.error}")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### System update
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# Fetch available extensions and their parameters
|
|
160
|
+
update_data = client.get_update_data()
|
|
161
|
+
|
|
162
|
+
for ext in update_data.extensions_with_parameters:
|
|
163
|
+
print(f"{ext.name}: {[p.name for p in ext.parameters]}")
|
|
164
|
+
|
|
165
|
+
# Execute an update with specific patches
|
|
166
|
+
result = client.execute_update(
|
|
167
|
+
patches={"Patch_MVP": "yes"},
|
|
168
|
+
create_essential_data=True,
|
|
169
|
+
create_project_data=True,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
print(f"Success: {result.success}")
|
|
173
|
+
print(result.log_text)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Poll the update log while a long-running update is in progress:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
import time
|
|
180
|
+
|
|
181
|
+
while True:
|
|
182
|
+
log = client.get_update_log()
|
|
183
|
+
print(log.log_text)
|
|
184
|
+
if log.is_complete:
|
|
185
|
+
break
|
|
186
|
+
time.sleep(5)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Session management
|
|
190
|
+
|
|
191
|
+
Sessions are cached to `~/.cache/hac-client/` by default so subsequent runs skip authentication:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
# Sessions are cached per (base_url, username, environment) tuple
|
|
195
|
+
client = HacClient(
|
|
196
|
+
base_url="https://localhost:9002",
|
|
197
|
+
auth_handler=auth,
|
|
198
|
+
environment="local", # tag sessions by environment
|
|
199
|
+
session_persistence=True, # enable caching (default)
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Manage the session cache directly:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from hac_client_core import SessionManager
|
|
207
|
+
|
|
208
|
+
mgr = SessionManager()
|
|
209
|
+
|
|
210
|
+
# List all cached sessions
|
|
211
|
+
for s in mgr.list_sessions():
|
|
212
|
+
print(f"{s.environment} {s.base_url} created={s.created_at_formatted}")
|
|
213
|
+
|
|
214
|
+
# Clear all sessions
|
|
215
|
+
mgr.clear_all_sessions()
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Custom authentication
|
|
219
|
+
|
|
220
|
+
Implement `AuthHandler` to support any authentication scheme:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from hac_client_core import AuthHandler
|
|
224
|
+
import requests
|
|
225
|
+
|
|
226
|
+
class BearerTokenAuth(AuthHandler):
|
|
227
|
+
def __init__(self, token: str, username: str):
|
|
228
|
+
self._token = token
|
|
229
|
+
self._username = username
|
|
230
|
+
|
|
231
|
+
def apply_auth(self, request: requests.PreparedRequest) -> requests.PreparedRequest:
|
|
232
|
+
request.headers["Authorization"] = f"Bearer {self._token}"
|
|
233
|
+
return request
|
|
234
|
+
|
|
235
|
+
def get_initial_credentials(self) -> dict[str, str]:
|
|
236
|
+
return {"j_username": self._username, "j_password": self._token}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## API reference
|
|
240
|
+
|
|
241
|
+
### `HacClient`
|
|
242
|
+
|
|
243
|
+
| Parameter | Type | Default | Description |
|
|
244
|
+
|---|---|---|---|
|
|
245
|
+
| `base_url` | `str` | — | HAC base URL (e.g. `https://localhost:9002`) |
|
|
246
|
+
| `auth_handler` | `AuthHandler` | — | Authentication handler |
|
|
247
|
+
| `environment` | `str` | `"local"` | Environment name for session caching |
|
|
248
|
+
| `timeout` | `int` | `30` | HTTP timeout in seconds |
|
|
249
|
+
| `ignore_ssl` | `bool` | `False` | Skip SSL certificate verification |
|
|
250
|
+
| `session_persistence` | `bool` | `True` | Cache sessions to disk |
|
|
251
|
+
| `quiet` | `bool` | `False` | Suppress informational messages on stderr |
|
|
252
|
+
|
|
253
|
+
**Methods:**
|
|
254
|
+
|
|
255
|
+
| Method | Returns | Description |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| `login()` | `None` | Authenticate and establish a session |
|
|
258
|
+
| `execute_groovy(script, commit=False)` | `GroovyScriptResult` | Execute a Groovy script |
|
|
259
|
+
| `execute_flexiblesearch(query, max_count=200, locale="en")` | `FlexibleSearchResult` | Run a FlexibleSearch query |
|
|
260
|
+
| `import_impex(impex_content, validation_mode="import_strict")` | `ImpexResult` | Import Impex data |
|
|
261
|
+
| `get_update_data()` | `UpdateData` | Fetch available update extensions and parameters |
|
|
262
|
+
| `execute_update(...)` | `UpdateResult` | Trigger a system update |
|
|
263
|
+
| `get_pending_patches()` | `dict` | Fetch pending system patches |
|
|
264
|
+
| `get_update_log()` | `UpdateLog` | Poll the current update log |
|
|
265
|
+
|
|
266
|
+
### Result models
|
|
267
|
+
|
|
268
|
+
All result dataclasses are importable from `hac_client_core`:
|
|
269
|
+
|
|
270
|
+
| Model | Key fields |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `GroovyScriptResult` | `output_text`, `execution_result`, `stacktrace_text`, `success` |
|
|
273
|
+
| `FlexibleSearchResult` | `headers`, `rows`, `result_count`, `exception`, `success` |
|
|
274
|
+
| `ImpexResult` | `success`, `output`, `error` |
|
|
275
|
+
| `UpdateData` | `is_initializing`, `project_datas`, `extensions_with_parameters` |
|
|
276
|
+
| `UpdateResult` | `success`, `log_text`, `is_finished` |
|
|
277
|
+
| `UpdateLog` | `log_text`, `is_complete`, `has_errors` |
|
|
278
|
+
|
|
279
|
+
### Exceptions
|
|
280
|
+
|
|
281
|
+
| Exception | Description |
|
|
282
|
+
|---|---|
|
|
283
|
+
| `HacClientError` | Base exception for all client errors |
|
|
284
|
+
| `HacAuthenticationError` | Authentication or session errors |
|
|
285
|
+
|
|
286
|
+
## Security considerations
|
|
287
|
+
|
|
288
|
+
- **Credentials in memory** — `BasicAuthHandler` clears its password reference on garbage collection. For stronger guarantees, implement a custom `AuthHandler` backed by a secrets manager.
|
|
289
|
+
- **CSRF protection** — the client automatically extracts and sends CSRF tokens on every request.
|
|
290
|
+
- **Session cache** — cached sessions are stored as JSON files in `~/.cache/hac-client/` with filesystem permissions of the current user. The cache contains session IDs and CSRF tokens — not passwords.
|
|
291
|
+
- **SSL verification** — `ignore_ssl=True` disables certificate checks. Use it only for local development with self-signed certificates.
|
|
292
|
+
|
|
293
|
+
## Development
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
# Install in editable mode with dev dependencies
|
|
297
|
+
pip install -e ".[dev]"
|
|
298
|
+
|
|
299
|
+
# Run tests
|
|
300
|
+
pytest
|
|
301
|
+
|
|
302
|
+
# Type checking
|
|
303
|
+
mypy src/
|
|
304
|
+
|
|
305
|
+
# Lint
|
|
306
|
+
ruff check src/
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# hac-client-core
|
|
2
|
+
|
|
3
|
+
Python client library for the **SAP Commerce HAC** (Hybris Administration Console) HTTP API.
|
|
4
|
+
|
|
5
|
+
Execute Groovy scripts, run FlexibleSearch queries, import Impex data, and trigger system updates — all from Python, with automatic session management and CSRF handling.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Groovy script execution** — run arbitrary Groovy in commit or rollback mode
|
|
10
|
+
- **FlexibleSearch queries** — execute queries with typed result objects
|
|
11
|
+
- **Impex import** — import Impex content with configurable validation
|
|
12
|
+
- **System update** — trigger updates, select patches/parameters, poll logs
|
|
13
|
+
- **Session management** — automatic login, CSRF tokens, session caching across runs
|
|
14
|
+
- **Pluggable authentication** — ships with Basic Auth; extend `AuthHandler` for OAuth, JWT, API keys, etc.
|
|
15
|
+
- **Fully typed** — complete type annotations with a `py.typed` marker (PEP 561)
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Python ≥ 3.12
|
|
20
|
+
- A running SAP Commerce instance with HAC enabled
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Install from PyPI (once published):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install hac-client-core
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or install directly from the repository:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install git+https://github.com/SapCommerceTools/ha-client-core.git
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For development:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/SapCommerceTools/ha-client-core.git
|
|
40
|
+
cd hac-client-core
|
|
41
|
+
pip install -e ".[dev]"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from hac_client_core import HacClient, BasicAuthHandler
|
|
48
|
+
|
|
49
|
+
# 1. Create an auth handler
|
|
50
|
+
auth = BasicAuthHandler("admin", "nimda")
|
|
51
|
+
|
|
52
|
+
# 2. Create the client
|
|
53
|
+
client = HacClient(
|
|
54
|
+
base_url="https://localhost:9002",
|
|
55
|
+
auth_handler=auth,
|
|
56
|
+
ignore_ssl=True, # skip certificate verification (dev only)
|
|
57
|
+
session_persistence=True, # cache sessions between runs
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# 3. Login (or let it happen automatically on the first call)
|
|
61
|
+
client.login()
|
|
62
|
+
|
|
63
|
+
# 4. Execute a Groovy script
|
|
64
|
+
result = client.execute_groovy("return 'Hello from HAC'")
|
|
65
|
+
print(result.execution_result) # "Hello from HAC"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
### Groovy script execution
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
result = client.execute_groovy(
|
|
74
|
+
script="return de.hybris.platform.core.Registry.applicationContext",
|
|
75
|
+
commit=False, # rollback mode (default)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if result.success:
|
|
79
|
+
print(result.output_text) # stdout output
|
|
80
|
+
print(result.execution_result) # return value
|
|
81
|
+
else:
|
|
82
|
+
print(result.stacktrace_text) # error details
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Set `commit=True` to execute in commit mode — changes will be persisted to the database.
|
|
86
|
+
|
|
87
|
+
### FlexibleSearch queries
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
result = client.execute_flexiblesearch(
|
|
91
|
+
query="SELECT {pk}, {code} FROM {Product} WHERE {code} LIKE '%camera%'",
|
|
92
|
+
max_count=50,
|
|
93
|
+
locale="en",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if result.success:
|
|
97
|
+
print(f"Columns: {result.headers}")
|
|
98
|
+
print(f"Rows returned: {result.result_count}")
|
|
99
|
+
for row in result.rows:
|
|
100
|
+
print(row)
|
|
101
|
+
else:
|
|
102
|
+
print(f"Query error: {result.exception}")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Impex import
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
impex = """
|
|
109
|
+
INSERT_UPDATE Product; code[unique=true]; name[lang=en]
|
|
110
|
+
; testProduct001 ; Test Product
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
result = client.import_impex(impex, validation_mode="import_strict")
|
|
114
|
+
|
|
115
|
+
if result.success:
|
|
116
|
+
print("Import completed")
|
|
117
|
+
else:
|
|
118
|
+
print(f"Import failed: {result.error}")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### System update
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
# Fetch available extensions and their parameters
|
|
125
|
+
update_data = client.get_update_data()
|
|
126
|
+
|
|
127
|
+
for ext in update_data.extensions_with_parameters:
|
|
128
|
+
print(f"{ext.name}: {[p.name for p in ext.parameters]}")
|
|
129
|
+
|
|
130
|
+
# Execute an update with specific patches
|
|
131
|
+
result = client.execute_update(
|
|
132
|
+
patches={"Patch_MVP": "yes"},
|
|
133
|
+
create_essential_data=True,
|
|
134
|
+
create_project_data=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
print(f"Success: {result.success}")
|
|
138
|
+
print(result.log_text)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Poll the update log while a long-running update is in progress:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import time
|
|
145
|
+
|
|
146
|
+
while True:
|
|
147
|
+
log = client.get_update_log()
|
|
148
|
+
print(log.log_text)
|
|
149
|
+
if log.is_complete:
|
|
150
|
+
break
|
|
151
|
+
time.sleep(5)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Session management
|
|
155
|
+
|
|
156
|
+
Sessions are cached to `~/.cache/hac-client/` by default so subsequent runs skip authentication:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# Sessions are cached per (base_url, username, environment) tuple
|
|
160
|
+
client = HacClient(
|
|
161
|
+
base_url="https://localhost:9002",
|
|
162
|
+
auth_handler=auth,
|
|
163
|
+
environment="local", # tag sessions by environment
|
|
164
|
+
session_persistence=True, # enable caching (default)
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Manage the session cache directly:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from hac_client_core import SessionManager
|
|
172
|
+
|
|
173
|
+
mgr = SessionManager()
|
|
174
|
+
|
|
175
|
+
# List all cached sessions
|
|
176
|
+
for s in mgr.list_sessions():
|
|
177
|
+
print(f"{s.environment} {s.base_url} created={s.created_at_formatted}")
|
|
178
|
+
|
|
179
|
+
# Clear all sessions
|
|
180
|
+
mgr.clear_all_sessions()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Custom authentication
|
|
184
|
+
|
|
185
|
+
Implement `AuthHandler` to support any authentication scheme:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from hac_client_core import AuthHandler
|
|
189
|
+
import requests
|
|
190
|
+
|
|
191
|
+
class BearerTokenAuth(AuthHandler):
|
|
192
|
+
def __init__(self, token: str, username: str):
|
|
193
|
+
self._token = token
|
|
194
|
+
self._username = username
|
|
195
|
+
|
|
196
|
+
def apply_auth(self, request: requests.PreparedRequest) -> requests.PreparedRequest:
|
|
197
|
+
request.headers["Authorization"] = f"Bearer {self._token}"
|
|
198
|
+
return request
|
|
199
|
+
|
|
200
|
+
def get_initial_credentials(self) -> dict[str, str]:
|
|
201
|
+
return {"j_username": self._username, "j_password": self._token}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## API reference
|
|
205
|
+
|
|
206
|
+
### `HacClient`
|
|
207
|
+
|
|
208
|
+
| Parameter | Type | Default | Description |
|
|
209
|
+
|---|---|---|---|
|
|
210
|
+
| `base_url` | `str` | — | HAC base URL (e.g. `https://localhost:9002`) |
|
|
211
|
+
| `auth_handler` | `AuthHandler` | — | Authentication handler |
|
|
212
|
+
| `environment` | `str` | `"local"` | Environment name for session caching |
|
|
213
|
+
| `timeout` | `int` | `30` | HTTP timeout in seconds |
|
|
214
|
+
| `ignore_ssl` | `bool` | `False` | Skip SSL certificate verification |
|
|
215
|
+
| `session_persistence` | `bool` | `True` | Cache sessions to disk |
|
|
216
|
+
| `quiet` | `bool` | `False` | Suppress informational messages on stderr |
|
|
217
|
+
|
|
218
|
+
**Methods:**
|
|
219
|
+
|
|
220
|
+
| Method | Returns | Description |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `login()` | `None` | Authenticate and establish a session |
|
|
223
|
+
| `execute_groovy(script, commit=False)` | `GroovyScriptResult` | Execute a Groovy script |
|
|
224
|
+
| `execute_flexiblesearch(query, max_count=200, locale="en")` | `FlexibleSearchResult` | Run a FlexibleSearch query |
|
|
225
|
+
| `import_impex(impex_content, validation_mode="import_strict")` | `ImpexResult` | Import Impex data |
|
|
226
|
+
| `get_update_data()` | `UpdateData` | Fetch available update extensions and parameters |
|
|
227
|
+
| `execute_update(...)` | `UpdateResult` | Trigger a system update |
|
|
228
|
+
| `get_pending_patches()` | `dict` | Fetch pending system patches |
|
|
229
|
+
| `get_update_log()` | `UpdateLog` | Poll the current update log |
|
|
230
|
+
|
|
231
|
+
### Result models
|
|
232
|
+
|
|
233
|
+
All result dataclasses are importable from `hac_client_core`:
|
|
234
|
+
|
|
235
|
+
| Model | Key fields |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `GroovyScriptResult` | `output_text`, `execution_result`, `stacktrace_text`, `success` |
|
|
238
|
+
| `FlexibleSearchResult` | `headers`, `rows`, `result_count`, `exception`, `success` |
|
|
239
|
+
| `ImpexResult` | `success`, `output`, `error` |
|
|
240
|
+
| `UpdateData` | `is_initializing`, `project_datas`, `extensions_with_parameters` |
|
|
241
|
+
| `UpdateResult` | `success`, `log_text`, `is_finished` |
|
|
242
|
+
| `UpdateLog` | `log_text`, `is_complete`, `has_errors` |
|
|
243
|
+
|
|
244
|
+
### Exceptions
|
|
245
|
+
|
|
246
|
+
| Exception | Description |
|
|
247
|
+
|---|---|
|
|
248
|
+
| `HacClientError` | Base exception for all client errors |
|
|
249
|
+
| `HacAuthenticationError` | Authentication or session errors |
|
|
250
|
+
|
|
251
|
+
## Security considerations
|
|
252
|
+
|
|
253
|
+
- **Credentials in memory** — `BasicAuthHandler` clears its password reference on garbage collection. For stronger guarantees, implement a custom `AuthHandler` backed by a secrets manager.
|
|
254
|
+
- **CSRF protection** — the client automatically extracts and sends CSRF tokens on every request.
|
|
255
|
+
- **Session cache** — cached sessions are stored as JSON files in `~/.cache/hac-client/` with filesystem permissions of the current user. The cache contains session IDs and CSRF tokens — not passwords.
|
|
256
|
+
- **SSL verification** — `ignore_ssl=True` disables certificate checks. Use it only for local development with self-signed certificates.
|
|
257
|
+
|
|
258
|
+
## Development
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Install in editable mode with dev dependencies
|
|
262
|
+
pip install -e ".[dev]"
|
|
263
|
+
|
|
264
|
+
# Run tests
|
|
265
|
+
pytest
|
|
266
|
+
|
|
267
|
+
# Type checking
|
|
268
|
+
mypy src/
|
|
269
|
+
|
|
270
|
+
# Lint
|
|
271
|
+
ruff check src/
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hac-client-core"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client library for the SAP Commerce HAC (Hybris Administration Console) HTTP API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "SapCommerceTools"},
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"sap",
|
|
17
|
+
"commerce",
|
|
18
|
+
"hybris",
|
|
19
|
+
"hac",
|
|
20
|
+
"administration",
|
|
21
|
+
"groovy",
|
|
22
|
+
"flexiblesearch",
|
|
23
|
+
"impex",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
33
|
+
"Typing :: Typed",
|
|
34
|
+
]
|
|
35
|
+
dependencies = [
|
|
36
|
+
"requests>=2.31.0",
|
|
37
|
+
"beautifulsoup4>=4.12.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=7.0",
|
|
43
|
+
"pytest-cov>=4.0",
|
|
44
|
+
"responses>=0.23.0",
|
|
45
|
+
"mypy>=1.0",
|
|
46
|
+
"ruff>=0.1.0",
|
|
47
|
+
]
|
|
48
|
+
docs = [
|
|
49
|
+
"mkdocs-material>=9.5",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://github.com/SapCommerceTools/ha-client-core"
|
|
54
|
+
Documentation = "https://sapcommercetools.github.io/hac-client-core/"
|
|
55
|
+
Repository = "https://github.com/SapCommerceTools/ha-client-core"
|
|
56
|
+
Issues = "https://github.com/SapCommerceTools/ha-client-core/issues"
|
|
57
|
+
Changelog = "https://github.com/SapCommerceTools/ha-client-core/blob/main/CHANGELOG.md"
|
|
58
|
+
|
|
59
|
+
[tool.setuptools.packages.find]
|
|
60
|
+
where = ["src"]
|
|
61
|
+
|
|
62
|
+
[tool.setuptools.package-data]
|
|
63
|
+
hac_client_core = ["py.typed"]
|
|
64
|
+
|
|
65
|
+
[tool.mypy]
|
|
66
|
+
python_version = "3.12"
|
|
67
|
+
strict = true
|
|
68
|
+
warn_return_any = true
|
|
69
|
+
warn_unused_configs = true
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
target-version = "py312"
|
|
73
|
+
line-length = 100
|
|
74
|
+
|
|
75
|
+
[tool.ruff.lint]
|
|
76
|
+
select = ["E", "F", "I", "N", "W", "UP", "B", "SIM", "TCH"]
|
|
77
|
+
|
|
78
|
+
[tool.pytest.ini_options]
|
|
79
|
+
testpaths = ["tests"]
|