zosmfrestapi 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,320 @@
1
+ from zosmfrestapi.models.workflow import (
2
+ WorkflowArchivedListItem,
3
+ WorkflowInstance,
4
+ WorkflowListItem,
5
+ )
6
+
7
+
8
+ def make_workflow_list():
9
+ return [
10
+ WorkflowListItem(
11
+ instanceURI="/zosmf/workflow/rest/1.0/workflows/"
12
+ "8ed3a85e-6472-47eb-ba44-19e9511df450",
13
+ owner="FOOBAR",
14
+ vendor="Hooli Software",
15
+ workflowDefinitionFileMD5Value="c87342978ad879837a9872899cse2001",
16
+ workflowDescription="The Box III, Gavin Belson Signature Edition",
17
+ workflowID="HOOLMC",
18
+ workflowKey="8ed3a85e-6472-47eb-ba44-19e9511df450",
19
+ workflowName="HOOLIE",
20
+ workflowVersion="1.0",
21
+ ),
22
+ ]
23
+
24
+
25
+ def make_workflow(steps=[], variables=[]):
26
+ return WorkflowInstance(
27
+ name="HOOLIE",
28
+ percentComplete=0,
29
+ workflowDefinitionFileMD5Value="8e8fabbd0f72c54119a3748427fc0f25",
30
+ workflowArchiveSAFID="FOOBAR",
31
+ jobStatement=None,
32
+ globalVariableGroup=None,
33
+ jobsOutputDirectory="/foo/bar/log",
34
+ scope="sysplex",
35
+ status="in-progress",
36
+ vendor="Hooli Software",
37
+ deleteCompletedJobs=False,
38
+ autoDeleteOnCompletion=False,
39
+ isInstanceVariableWithoutPrefix=False,
40
+ workflowVersion="1.0",
41
+ steps=steps,
42
+ access="Public",
43
+ productVersion=None,
44
+ productID=None,
45
+ variables=variables,
46
+ category="general",
47
+ system="TST01.TSTA",
48
+ workflowDescription="The Box III, Gavin Belson Signature Edition",
49
+ workflowID="Hoolie",
50
+ owner="FOOBAR",
51
+ accountInfo=None,
52
+ automationStatus=None,
53
+ containsParallelSteps=False,
54
+ isCallable=None,
55
+ workflowKey="fc4fc0fa-1fa3-4247-bb2d-7f5203e0c3c7",
56
+ productName=None,
57
+ )
58
+
59
+
60
+ def make_new_workflow():
61
+ return WorkflowInstance(
62
+ name="HOOLIE",
63
+ owner="demouser",
64
+ system="TST01.TSTA (TSTA)",
65
+ scope="sysplex",
66
+ percentComplete=0,
67
+ workflowDefinitionFileMD5Value="aca0b684798fb2dfd49261ca4d211d93",
68
+ workflowArchiveSAFID="demouser",
69
+ jobStatement=None,
70
+ globalVariableGroup=None,
71
+ jobsOutputDirectory=None,
72
+ status="in-progress",
73
+ vendor="Hoolie Software",
74
+ deleteCompletedJobs=False,
75
+ autoDeleteOnCompletion=False,
76
+ isInstanceVariableWithoutPrefix=False,
77
+ workflowVersion="1.0",
78
+ steps=[],
79
+ access="Public",
80
+ productVersion=None,
81
+ productID=None,
82
+ variables=[],
83
+ category="general",
84
+ workflowDescription="The Box III, Gavin Belson Signature Edition",
85
+ workflowID="Hoolie",
86
+ accountInfo=None,
87
+ automationStatus=None,
88
+ containsParallelSteps=False,
89
+ isCallable=None,
90
+ workflowKey="fc4fc0fa-1fa3-4247-bb2d-7f5203e0c3c7",
91
+ productName=None,
92
+ )
93
+
94
+
95
+ def make_workflow_with_steps():
96
+
97
+ step1 = """//*********************************************************************
98
+ //* STEP - Create temporary directory using BPXBATCH *
99
+ //*********************************************************************
100
+ //*
101
+ //CREATE EXEC PGM=BPXBATCH
102
+ //*
103
+ //STDOUT DD SYSOUT=*
104
+ //*
105
+ //STDERR DD SYSOUT=*
106
+ //*
107
+ //STDPARM DD *
108
+ SH mkdir -m 700 /tmp/IZUWORKFLOW-17932912593740.328682
109
+ /*
110
+ //*
111
+ //*********************************************************************
112
+ //* STEP - Create script in temp directory using BPXCOPY *
113
+ //*********************************************************************
114
+ //*
115
+ //UXCOPY EXEC PGM=BPXCOPY,
116
+ // PARM='ELEMENT(MYSCRIPT) TYPE(TEXT) PATHMODE(0,7,0,0)'
117
+ //*
118
+ //SYSTSPRT DD SYSOUT=*
119
+ //*
120
+ //SYSIN DD DUMMY
121
+ //*
122
+ //SYSUT2 DD PATH='/tmp/IZUWORKFLOW-17932912593740.328682'
123
+ //*
124
+ //SYSUT1 DD *,DLM=$$
125
+ /bin/echo 'HELLO WORLD 1'
126
+ $$
127
+ //*
128
+ //*********************************************************************
129
+ //* STEP - Execute script using BPXBATCH *
130
+ //*********************************************************************
131
+ //*
132
+ //XBATCH EXEC PGM=BPXBATCH
133
+ //*
134
+ //STDOUT DD SYSOUT=*
135
+ //*
136
+ //STDERR DD SYSOUT=*
137
+ //*
138
+ //STDPARM DD *
139
+ SH /tmp/IZUWORKFLOW-17932912593740.328682/MYSCRIPT
140
+ /*
141
+ //*
142
+ //*********************************************************************
143
+ //* STEP - Delete temporary directory using BPXBATCH *
144
+ //*********************************************************************
145
+ //*
146
+ //REMOVE EXEC PGM=BPXBATCH
147
+ //*
148
+ //STDOUT DD SYSOUT=*
149
+ //*
150
+ //STDERR DD SYSOUT=*
151
+ //*
152
+ //STDPARM DD *
153
+ SH rm -r /tmp/IZUWORKFLOW-17932912593740.328682
154
+ /*
155
+ //*
156
+ //*********************************************************************"""
157
+ step2 = """//*********************************************************************
158
+ //* STEP - Create temporary directory using BPXBATCH *
159
+ //*********************************************************************
160
+ //*
161
+ //CREATE EXEC PGM=BPXBATCH
162
+ //*
163
+ //STDOUT DD SYSOUT=*
164
+ //*
165
+ //STDERR DD SYSOUT=*
166
+ //*
167
+ //STDPARM DD *
168
+ SH mkdir -m 700 /tmp/IZUWORKFLOW-17932912593750.432322
169
+ /*
170
+ //*
171
+ //*********************************************************************
172
+ //* STEP - Create script in temp directory using BPXCOPY *
173
+ //*********************************************************************
174
+ //*
175
+ //UXCOPY EXEC PGM=BPXCOPY,
176
+ // PARM='ELEMENT(MYSCRIPT) TYPE(TEXT) PATHMODE(0,7,0,0)'
177
+ //*
178
+ //SYSTSPRT DD SYSOUT=*
179
+ //*
180
+ //SYSIN DD DUMMY
181
+ //*
182
+ //SYSUT2 DD PATH='/tmp/IZUWORKFLOW-17932912593750.432322'
183
+ //*
184
+ //SYSUT1 DD *,DLM=$$
185
+ /bin/echo 'HELLO WORLD 2'
186
+ $$
187
+ //*
188
+ //*********************************************************************
189
+ //* STEP - Execute script using BPXBATCH *
190
+ //*********************************************************************
191
+ //*
192
+ //XBATCH EXEC PGM=BPXBATCH
193
+ //*
194
+ //STDOUT DD SYSOUT=*
195
+ //*
196
+ //STDERR DD SYSOUT=*
197
+ //*
198
+ //STDPARM DD *
199
+ SH /tmp/IZUWORKFLOW-17932912593750.432322/MYSCRIPT
200
+ /*
201
+ //*
202
+ //*********************************************************************
203
+ //* STEP - Delete temporary directory using BPXBATCH *
204
+ //*********************************************************************
205
+ //*
206
+ //REMOVE EXEC PGM=BPXBATCH
207
+ //*
208
+ //STDOUT DD SYSOUT=*
209
+ //*
210
+ //STDERR DD SYSOUT=*
211
+ //*
212
+ //STDPARM DD *
213
+ SH rm -r /tmp/IZUWORKFLOW-17932912593750.432322
214
+ /*
215
+ //*
216
+ //*********************************************************************"""
217
+
218
+ return make_workflow(
219
+ steps=[
220
+ {
221
+ "template": step1,
222
+ "instructions": "",
223
+ "maxLrecl": 1024,
224
+ "failedPattern": [],
225
+ "description": "The Box III, Gavin Belson Signature Edition: because"
226
+ " ordinary excellence was never enough",
227
+ "outputVariablesPrefix": None,
228
+ "variable-references": None,
229
+ "saveAsUnixFileSub": False,
230
+ "procName": "IZUFPROC",
231
+ "title": "Infinite Scale",
232
+ "jobInfo": None,
233
+ "timeout": 0,
234
+ "regionSize": 0,
235
+ "skills": None,
236
+ "isRestStep": False,
237
+ "output": None,
238
+ "outputSub": False,
239
+ "returnCode": None,
240
+ "outputSysoutDD": False,
241
+ "successPattern": None,
242
+ "state": "Unassigned",
243
+ "templateSub": False,
244
+ "owner": None,
245
+ "autoEnable": True,
246
+ "submitAs": "shell-JCL",
247
+ "userDefined": False,
248
+ "weight": "1",
249
+ "optional": False,
250
+ "steps": None,
251
+ "scriptParameters": None,
252
+ "saveAsUnixFile": None,
253
+ "instructionsSub": False,
254
+ "saveAsDatasetSub": False,
255
+ "isConditionStep": False,
256
+ "prereqStep": None,
257
+ "hasCalledWorkflow": False,
258
+ "name": "step1",
259
+ "stepNumber": "1",
260
+ "saveAsDataset": None,
261
+ },
262
+ {
263
+ "template": step2,
264
+ "instructions": "",
265
+ "maxLrecl": 1024,
266
+ "failedPattern": [],
267
+ "description": "Engineered for those who don't follow the"
268
+ " future, but define it.",
269
+ "outputVariablesPrefix": None,
270
+ "variable-references": None,
271
+ "saveAsUnixFileSub": False,
272
+ "procName": "IZUFPROC",
273
+ "title": "Vision Engine",
274
+ "jobInfo": None,
275
+ "timeout": 0,
276
+ "regionSize": 0,
277
+ "skills": None,
278
+ "isRestStep": False,
279
+ "output": None,
280
+ "outputSub": False,
281
+ "returnCode": None,
282
+ "outputSysoutDD": False,
283
+ "successPattern": None,
284
+ "state": "Unassigned",
285
+ "templateSub": False,
286
+ "owner": None,
287
+ "autoEnable": True,
288
+ "submitAs": "shell-JCL",
289
+ "userDefined": False,
290
+ "weight": "1",
291
+ "optional": False,
292
+ "steps": None,
293
+ "scriptParameters": None,
294
+ "saveAsUnixFile": None,
295
+ "instructionsSub": False,
296
+ "saveAsDatasetSub": False,
297
+ "isConditionStep": False,
298
+ "prereqStep": None,
299
+ "hasCalledWorkflow": False,
300
+ "name": "step2",
301
+ "stepNumber": "2",
302
+ "saveAsDataset": None,
303
+ },
304
+ ]
305
+ )
306
+
307
+
308
+ def make_workflow_with_variables():
309
+ return make_workflow()
310
+
311
+
312
+ def make_archived_worfklow_list():
313
+ return [
314
+ WorkflowArchivedListItem(
315
+ archivedInstanceURI="/zosmf/workflow/rest/1.0/archivedworkflows/"
316
+ "07775b08-2dd9-4810-abb4-26f7cb05152a",
317
+ workflowKey="07775b08-2dd9-4810-abb4-26f7cb05152a",
318
+ workflowName="myworkflow",
319
+ )
320
+ ]
zosmfrestapi/utils.py ADDED
@@ -0,0 +1,76 @@
1
+ from dataclasses import fields, is_dataclass
2
+ from typing import Any
3
+
4
+ import pendulum
5
+
6
+
7
+ def _parse_datetime(value: str) -> pendulum.DateTime:
8
+ """Parse a string and require a Pendulum ``DateTime`` result.
9
+
10
+ Args:
11
+ value: Date/time string accepted by :func:`pendulum.parse`.
12
+
13
+ Returns:
14
+ The parsed timezone-aware or naive Pendulum ``DateTime`` value.
15
+
16
+ Raises:
17
+ TypeError: If Pendulum parses the input as a date, time, duration, or
18
+ another non-``DateTime`` value.
19
+ """
20
+ result = pendulum.parse(value)
21
+
22
+ if not isinstance(result, pendulum.DateTime):
23
+ raise TypeError(f"Expected DateTime, got {type(result)}")
24
+
25
+ return result
26
+
27
+
28
+ def remap(data: dict[str, Any], cls: type[Any]) -> dict[str, Any]:
29
+ """Translate JSON field aliases to the names used by a dataclass.
30
+
31
+ Dataclass fields with ``metadata={"json_name": ...}`` are mapped from
32
+ their external JSON key back to the Python field name. Other keys and
33
+ values are preserved.
34
+
35
+ Args:
36
+ data: Mapping-like dictionary received from an API response.
37
+ cls: Dataclass type whose field metadata defines the aliases.
38
+
39
+ Returns:
40
+ A new dictionary with aliased keys remapped; ``data`` is not modified.
41
+ """
42
+ aliases = {
43
+ f.metadata["json_name"]: f.name for f in fields(cls) if "json_name" in f.metadata
44
+ }
45
+
46
+ return {aliases.get(k, k): v for k, v in data.items()}
47
+
48
+
49
+ def to_dict(obj: object) -> Any:
50
+ """Recursively convert dataclasses and containers to JSON payload values.
51
+
52
+ Dataclass fields are converted to dictionaries, respecting each field's
53
+ ``json_name`` metadata. Lists and dictionaries are converted recursively;
54
+ other values are returned unchanged.
55
+
56
+ Args:
57
+ obj: Dataclass instance, list, dictionary, or scalar value to convert.
58
+
59
+ Returns:
60
+ The recursively converted value. Leaves are preserved, so callers
61
+ should supply JSON-compatible scalar values when using the result as an
62
+ HTTP JSON body.
63
+ """
64
+ if is_dataclass(obj):
65
+ return {
66
+ f.metadata.get("json_name", f.name): to_dict(getattr(obj, f.name))
67
+ for f in fields(obj)
68
+ }
69
+
70
+ if isinstance(obj, list):
71
+ return [to_dict(item) for item in obj]
72
+
73
+ if isinstance(obj, dict):
74
+ return {key: to_dict(value) for key, value in obj.items()}
75
+
76
+ return obj
@@ -0,0 +1,55 @@
1
+ # pyright: reportMissingImports=false
2
+
3
+
4
+ import cryptography
5
+ from cryptography.hazmat.primitives import serialization
6
+
7
+ USE_MODERN_CRYPTOGRAPHY = cryptography.__version__ != "3.3.2"
8
+
9
+
10
+ def _extract_key_from_pkcs12(
11
+ p12_data: bytes,
12
+ password: str,
13
+ ):
14
+ if USE_MODERN_CRYPTOGRAPHY:
15
+ # Use cryptography.pkcs12.load_key_and_certificates() whenever
16
+ # a modern cryptography release is available.
17
+ #
18
+ from cryptography.hazmat.primitives.serialization import pkcs12
19
+
20
+ private_key, _, _ = pkcs12.load_key_and_certificates(
21
+ p12_data,
22
+ password.encode(),
23
+ )
24
+ if private_key is None:
25
+ raise ValueError("PKCS#12 does not contain a private key")
26
+
27
+ return private_key.private_bytes(
28
+ encoding=serialization.Encoding.PEM,
29
+ format=serialization.PrivateFormat.PKCS8,
30
+ encryption_algorithm=serialization.BestAvailableEncryption(password.encode()),
31
+ ).decode("utf-8")
32
+
33
+ else: # pragma: no cover
34
+ # Fallback to OpenSSL.crypto.load_pkcs12() on legacy z/OS
35
+ # environments where only older cryptography versions are
36
+ # available.
37
+ #
38
+ # This fallback exists for two reasons:
39
+ # - Older z/OS environments could not easily use modern
40
+ # cryptography releases due to the Rust dependency.
41
+ # - cryptography 3.3.2 showed extremely poor PKCS#12
42
+ # performance on z/OS (~15 seconds versus <1 second
43
+ # with PyOpenSSL).
44
+ #
45
+ # Note that OpenSSL.crypto.load_pkcs12() was removed from
46
+ # PyOpenSSL 23.3.0 (2023-10-25). This code path is therefore
47
+ # intended solely for legacy environments.
48
+ from OpenSSL import crypto
49
+
50
+ return crypto.dump_privatekey(
51
+ crypto.FILETYPE_PEM,
52
+ crypto.load_pkcs12(p12_data, password.encode()).get_privatekey(),
53
+ cipher="aes-256-cbc",
54
+ passphrase=password.encode(),
55
+ ).decode("utf-8")
@@ -0,0 +1,68 @@
1
+ import platform
2
+ import secrets
3
+ import ssl
4
+ from tempfile import NamedTemporaryFile
5
+
6
+ from zosmfrestapi.client import ZosmfClient
7
+ from zosmfrestapi.zcrypto import _extract_key_from_pkcs12
8
+
9
+
10
+ class ZosmfZcryptoClient(ZosmfClient):
11
+ """Create an HTTP client for communicating with z/OSMF using a
12
+ client certificate stored in a SAF key ring.
13
+
14
+ This class behaves like ``ZosmfClient`` but retrieves the client
15
+ certificate and private key from a SAF key ring using ``py_zcrypto``.
16
+ The certificate material is exported to temporary PEM files and
17
+ configured automatically for mutual TLS (mTLS) authentication.
18
+
19
+ Example:
20
+ Use a client with mTLS authentication using a certificate stored
21
+ in a SAF key ring:
22
+
23
+ with ZosmfClient(
24
+ "https://zosmf.example",
25
+ keyring="USERID/SAF_key_ring_name",
26
+ label="Certificate label",
27
+ ) as client:
28
+ systems = client.get_systems()
29
+
30
+ Additional Args:
31
+ keyring: SAF key ring name. The format can be either
32
+ ``keyring_name`` or ``USERID/keyring_name``.
33
+
34
+ label: Label of the certificate in the SAF key ring.
35
+
36
+ Notes:
37
+ The ``zcrypto`` optional dependency must be installed:
38
+
39
+ pip install "zosmfrestapi[zcrypto]"
40
+
41
+ All other arguments are inherited from ``ZosmfClient``.
42
+ """
43
+
44
+ def __init__(self, *args, **kwargs):
45
+ if platform.system() != "OS/390":
46
+ raise RuntimeError("The zcrypto backend is only available on z/OS.")
47
+ import py_zcrypto # pyright: ignore[reportMissingImports]
48
+
49
+ keyring = kwargs.pop("keyring")
50
+ label = kwargs.pop("label")
51
+ zcrypto_obj = py_zcrypto.zcrypto()
52
+ zcrypto_obj.open_key_ring(keyring)
53
+ password = secrets.token_urlsafe(32)
54
+ with NamedTemporaryFile(mode="w") as key_pem, NamedTemporaryFile(mode="w") as cert_pem:
55
+
56
+ key = _extract_key_from_pkcs12(
57
+ bytes(zcrypto_obj.export_key_to_buffer(password, label)), password
58
+ )
59
+ cert = ssl.DER_cert_to_PEM_cert(bytes(zcrypto_obj.export_cert_to_buffer(label)))
60
+
61
+ key_pem.write(key)
62
+ cert_pem.write(cert)
63
+ key_pem.flush()
64
+ cert_pem.flush()
65
+
66
+ super().__init__(
67
+ *args, **kwargs, cert=(cert_pem.name, key_pem.name), key_password=password
68
+ )
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: zosmfrestapi
3
+ Version: 0.2.0
4
+ Summary: A Python wrapper for the z/OSMF REST API
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: z/OSMF,IBM Z,mainframe,REST API
8
+ Author: Oscar Curero
9
+ Author-email: dev@curero.es
10
+ Requires-Python: >=3.12
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Provides-Extra: zcrypto
17
+ Requires-Dist: dacite (>=1.9.2,<2.0.0)
18
+ Requires-Dist: httpx2 (>=2.12.0,<3.0.0)
19
+ Requires-Dist: pendulum (>=3.2.0,<4.0.0)
20
+ Requires-Dist: py_zcrypto ; extra == "zcrypto"
21
+ Project-URL: Homepage, https://sr.ht/~ocurero/zosmfrestapi/
22
+ Project-URL: PyPI, https://pypi.org/project/zosmfrestapi/
23
+ Project-URL: Repository, https://sr.ht/~ocurero/zosmfrestapi/
24
+ Description-Content-Type: text/markdown
25
+
26
+ # A Python wrapper for the z/OSMF© REST API
27
+
28
+ [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
29
+ [![builds.sr.ht status](https://builds.sr.ht/~ocurero/zosmfrestapi/commits/main/.build.yml.svg)](https://builds.sr.ht/~ocurero/zosmfrestapi/commits/main/.build.yml?)
30
+ ![z/OS](https://img.shields.io/badge/works%20on-z/OS-8A2BE2)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
32
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
33
+
34
+ * Open Source: MIT License.
35
+ * Website: <https://sr.ht/~ocurero/zosmfrestapi/>
36
+ * Pypi: <https://pypi.org/project/zosmfrestapi/>
37
+ * Changelog: [CHANGELOG.md](CHANGELOG.md)
38
+ * Contributing: [CONTRIBUTING.md](CONTRIBUTING.md)
39
+
40
+ Key features:
41
+ * **Pure Python**: Package and its dependencies are 100% pure Python (no C extensions required).
42
+ * **Cross-platform**: Works on Windows, Linux, macOS, and z/OS UNIX System Services.
43
+ * **Flexible authentication**: Supports user/password, LTPA and JWT tokens, and mutual TLS (mTLS).
44
+ * **Proxy ready**: Full support for standard HTTP/HTTPS proxies as well as authenticated proxies.
45
+
46
+
47
+ Install
48
+ -------
49
+
50
+ **zosmfrestapi** requires Python 3.12 or newer and access to a z/OSMF server.
51
+
52
+ Install the package from PyPI:
53
+
54
+ ```console
55
+ python -m pip install zosmfrestapi
56
+ ```
57
+
58
+ If you have downloaded or cloned the source code, run this from the project's
59
+ root folder instead:
60
+
61
+ ```console
62
+ python -m pip install .
63
+ ```
64
+
65
+ ## First request
66
+
67
+ Save this as `example.py` and run it with `python example.py`. Enter the base
68
+ URL of your z/OSMF server when prompted (for example,
69
+ `https://zosmf.example.com:443`). The URL should include `https://` and any
70
+ required port, but not an API path such as `/zosmf`.
71
+
72
+ The password is requested without displaying it and is not stored in the
73
+ script. Replace the sample operation with one your z/OSMF user is authorized to
74
+ perform.
75
+
76
+ ```python
77
+ from getpass import getpass
78
+
79
+ from zosmfrestapi import ZosmfClient, ZosmfError
80
+
81
+ server = input("z/OSMF server URL: ").strip().rstrip("/")
82
+ username = input("User ID: ").strip()
83
+ password = getpass("Password: ")
84
+
85
+ try:
86
+ with ZosmfClient(server, username=username, password=password) as client:
87
+ for instance in client.list_swis():
88
+ print(f"{instance.name} ({instance.system})")
89
+ except ZosmfError as error:
90
+ print(f"The z/OSMF request failed: {error}")
91
+ ```
92
+
93
+ The `with` block closes the network connection automatically, even if a request
94
+ fails. `list_swis()` returns Python objects; the example prints each software
95
+ instance's name and system. You can also query systems or read a data set. Put
96
+ these calls inside the `with` block shown above, before it ends:
97
+
98
+ ```python
99
+ systems = client.get_systems()
100
+ contents = client.read_dataset("USER.DATASET")
101
+ ```
102
+
103
+ These calls must be made inside the `with` block, before the client is closed.
104
+ For a partitioned data set member, use a name such as
105
+ `USER.PDS(MEMBER)`.
106
+
107
+ ## Authentication and TLS
108
+
109
+ The client uses the server's normal TLS certificate checks by default. Keep
110
+ those checks enabled for routine use. The HTTP transport is HTTPX2, a fork of
111
+ HTTPX; HTTPX2 uses `truststore` to integrate operating-system certificate
112
+ stores when it creates its default TLS context. This package currently passes
113
+ its own context, created with Python's `ssl.create_default_context()`, so the
114
+ certificates available to Python are used. If the z/OSMF server's CA is not
115
+ trusted in that environment, pass its certificate file with `cafile` below.
116
+
117
+ | Situation | Client option |
118
+ | --- | --- |
119
+ | User ID and password | `username="...", password="..."` |
120
+ | Client certificate and key in separate files | `cert=("client.crt", "client.key")` |
121
+ | Client certificate and key in one PEM file | `cert="client.pem"` |
122
+ | Internal certificate authority | `cafile="company-ca.pem"` |
123
+ | Existing LTPA or JWT token | `ltpa_token="..."` or `jwt_token="..."` |
124
+ | Network proxy | `proxy="http://proxy.example.com:8080"` |
125
+
126
+ For example, to trust an internal certificate authority, pass `cafile` when
127
+ creating the client:
128
+
129
+ ```python
130
+ with ZosmfClient(server, username=username, password=password,
131
+ cafile="company-ca.pem") as client:
132
+ systems = client.get_systems()
133
+ ```
134
+
135
+ Avoid putting passwords or tokens directly in scripts, committing them to
136
+ version control, or sharing them in logs. The `insecure=True` option disables
137
+ TLS certificate verification; use it only for temporary diagnostics, never as
138
+ a production fix. It cannot be combined with `cert` or `cafile`.
139
+
140
+ ## What you can do
141
+
142
+ The client provides synchronous methods for:
143
+
144
+ * Authentication (`authenticate`).
145
+ * z/OSMF groups and systems (`get_groups`, `get_systems`).
146
+ * Data sets (`read_dataset`).
147
+ * Software instances and SMP/E information (`list_swis`, `get_swi_by_name`,
148
+ `get_swi_by_uuid`, and related query methods).
149
+ * Workflows (list, inspect, create, start, wait for, cancel, and archive).
150
+ * Software updates (search, inspect, start, monitor, and cancel).
151
+
152
+ Some operations change z/OSMF state. Check the method documentation and make
153
+ sure your account has the required permissions before using them.
154
+
155
+ ## Troubleshooting
156
+
157
+ * **Python version error:** install Python 3.12 or newer, then recreate the
158
+ virtual environment with that interpreter.
159
+ * **401 / unauthorized:** check the server URL, user ID, password or token, and
160
+ whether the account is authorized for the requested API.
161
+ * **Certificate verification error:** ask your z/OSMF administrator for the
162
+ correct CA certificate and pass its path with `cafile`.
163
+ * **Connection or timeout error:** check that the server and port are reachable
164
+ from your machine and that any required proxy is configured.
165
+ * **PowerShell will not activate the virtual environment:** use the Python
166
+ executable inside `.venv\Scripts\python.exe` to run commands, or follow your
167
+ organization's PowerShell policy guidance. Do not change the machine-wide
168
+ execution policy just to install this package.
169
+
170
+ For the complete API, parameters, and return types, see the docstrings in the
171
+ package source and the project website.
172
+