python-rundeck 0.3.0__tar.gz → 1.0.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.
Files changed (28) hide show
  1. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/PKG-INFO +41 -4
  2. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/README.md +40 -3
  3. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/pyproject.toml +1 -1
  4. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/base.py +5 -3
  5. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/executions.py +2 -3
  6. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/jobs.py +24 -6
  7. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/key_storage.py +33 -12
  8. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/system.py +6 -0
  9. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/users.py +5 -4
  10. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/LICENSE +0 -0
  11. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/__init__.py +0 -0
  12. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/client.py +0 -0
  13. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/config.py +0 -0
  14. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/const.py +0 -0
  15. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/exceptions.py +0 -0
  16. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/py.typed +0 -0
  17. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/__init__.py +0 -0
  18. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/__init__.py +0 -0
  19. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/adhoc.py +0 -0
  20. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/config_management.py +0 -0
  21. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/features.py +0 -0
  22. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/metrics.py +0 -0
  23. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/plugins.py +0 -0
  24. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/projects.py +0 -0
  25. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/scheduler.py +0 -0
  26. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/scm.py +0 -0
  27. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/tokens.py +0 -0
  28. {python_rundeck-0.3.0 → python_rundeck-1.0.0}/src/rundeck/v1/objects/webhooks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-rundeck
3
- Version: 0.3.0
3
+ Version: 1.0.0
4
4
  Summary: Python client for the Rundeck API (v14-v56).
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -51,6 +51,7 @@ Contents
51
51
  - Quick start
52
52
  - Configuration
53
53
  - Available resources
54
+ - Return types policy
54
55
  - Examples by resource
55
56
  - Error handling
56
57
  - Development and testing
@@ -80,7 +81,8 @@ for p in projects:
80
81
  # Get a project and run a job
81
82
  project = rd.projects.get("demo")
82
83
  jobs = project.jobs.list()
83
- execu = jobs[0].run()
84
+ # Use as_execution=True to receive an Execution object.
85
+ execu = jobs[0].run(as_execution=True)
84
86
  print(execu.id)
85
87
  ```
86
88
 
@@ -122,6 +124,40 @@ Available resources
122
124
  - `config_management` (`ConfigManagementManager`): global configuration `/config`.
123
125
  - `scm` (via `project.scm` and `job.scm`): import/export plugins, setup, enable/disable, status, actions (commit/import/export...).
124
126
 
127
+ Return types policy
128
+ -------------------
129
+ - Resource managers use `list/get/create/update` to return objects (`RundeckObject`).
130
+ - Factory managers return the created resource object for actions that create it
131
+ (e.g., `project.adhoc` returns `Execution`).
132
+ - Utility managers return raw API responses (dict/str/bytes).
133
+ - `delete(...)` returns `None`.
134
+ - Object methods return raw API responses and update the object in place when needed.
135
+ - Explicit flags are used when an object return is requested (e.g., `as_execution=True`).
136
+
137
+ Resource managers:
138
+ - `projects`, `jobs`, `executions`, `tokens`, `users`, `plugins`, `features`, `key_storage`, `system.acl`, `project.webhooks`.
139
+
140
+ Factory managers:
141
+ - `adhoc` (via `project.adhoc`).
142
+ Returns `Execution` objects (execution factory).
143
+
144
+ Utility managers:
145
+ - `system`, `config_management`, `metrics`, `scheduler`, `webhooks` (event send).
146
+ - `scm` (via `project.scm` and `job.scm`), and project sub-managers like
147
+ `project.config`, `project.resources`, `project.sources`, `project.readme`,
148
+ `project.archive`, `project.acl`.
149
+
150
+ Note: `project.webhooks.create(...)` returns the raw API response (e.g., `{"msg": "Saved webhook"}`)
151
+ because the Rundeck API does not return the created webhook object.
152
+
153
+ Example:
154
+ ```python
155
+ job = rd.jobs.get("job-id") # -> Job (object)
156
+ resp = job.run() # -> dict (raw API response)
157
+ execu = job.run(as_execution=True) # -> Execution (object)
158
+ execution.refresh() # updates in place, returns None
159
+ ```
160
+
125
161
  Examples by resource (complete)
126
162
  -------------------------------
127
163
  Projects
@@ -240,7 +276,7 @@ project.jobs.import_jobs(content=open("jobs.json", "rb").read(), fileformat="jso
240
276
  Note: import remains exposed as `import_jobs(...)` (the Python keyword prevents a direct call to `.import`). If you prefer the alias, use `getattr(rd.jobs, "import")(...)`.
241
277
 
242
278
  # Run and get the execution
243
- execution = job.run(argString="-option value")
279
+ execution = job.run(as_execution=True, argString="-option value")
244
280
 
245
281
  # Bulk actions
246
282
  rd.jobs.bulk.enable_execution(["id1", "id2"])
@@ -398,7 +434,8 @@ ks = rd.key_storage
398
434
  # Create a secret (password)
399
435
  ks.create("integration/secret1", content="s3cr3t", content_type="application/x-rundeck-data-password")
400
436
  resources = ks.list() # root listing
401
- meta = ks.get_metadata("integration/secret1")
437
+ meta = ks.get("integration/secret1")
438
+ content = meta.content() # raw bytes
402
439
  ks.delete("integration/secret1")
403
440
 
404
441
  # AdHoc commands/scripts
@@ -13,6 +13,7 @@ Contents
13
13
  - Quick start
14
14
  - Configuration
15
15
  - Available resources
16
+ - Return types policy
16
17
  - Examples by resource
17
18
  - Error handling
18
19
  - Development and testing
@@ -42,7 +43,8 @@ for p in projects:
42
43
  # Get a project and run a job
43
44
  project = rd.projects.get("demo")
44
45
  jobs = project.jobs.list()
45
- execu = jobs[0].run()
46
+ # Use as_execution=True to receive an Execution object.
47
+ execu = jobs[0].run(as_execution=True)
46
48
  print(execu.id)
47
49
  ```
48
50
 
@@ -84,6 +86,40 @@ Available resources
84
86
  - `config_management` (`ConfigManagementManager`): global configuration `/config`.
85
87
  - `scm` (via `project.scm` and `job.scm`): import/export plugins, setup, enable/disable, status, actions (commit/import/export...).
86
88
 
89
+ Return types policy
90
+ -------------------
91
+ - Resource managers use `list/get/create/update` to return objects (`RundeckObject`).
92
+ - Factory managers return the created resource object for actions that create it
93
+ (e.g., `project.adhoc` returns `Execution`).
94
+ - Utility managers return raw API responses (dict/str/bytes).
95
+ - `delete(...)` returns `None`.
96
+ - Object methods return raw API responses and update the object in place when needed.
97
+ - Explicit flags are used when an object return is requested (e.g., `as_execution=True`).
98
+
99
+ Resource managers:
100
+ - `projects`, `jobs`, `executions`, `tokens`, `users`, `plugins`, `features`, `key_storage`, `system.acl`, `project.webhooks`.
101
+
102
+ Factory managers:
103
+ - `adhoc` (via `project.adhoc`).
104
+ Returns `Execution` objects (execution factory).
105
+
106
+ Utility managers:
107
+ - `system`, `config_management`, `metrics`, `scheduler`, `webhooks` (event send).
108
+ - `scm` (via `project.scm` and `job.scm`), and project sub-managers like
109
+ `project.config`, `project.resources`, `project.sources`, `project.readme`,
110
+ `project.archive`, `project.acl`.
111
+
112
+ Note: `project.webhooks.create(...)` returns the raw API response (e.g., `{"msg": "Saved webhook"}`)
113
+ because the Rundeck API does not return the created webhook object.
114
+
115
+ Example:
116
+ ```python
117
+ job = rd.jobs.get("job-id") # -> Job (object)
118
+ resp = job.run() # -> dict (raw API response)
119
+ execu = job.run(as_execution=True) # -> Execution (object)
120
+ execution.refresh() # updates in place, returns None
121
+ ```
122
+
87
123
  Examples by resource (complete)
88
124
  -------------------------------
89
125
  Projects
@@ -202,7 +238,7 @@ project.jobs.import_jobs(content=open("jobs.json", "rb").read(), fileformat="jso
202
238
  Note: import remains exposed as `import_jobs(...)` (the Python keyword prevents a direct call to `.import`). If you prefer the alias, use `getattr(rd.jobs, "import")(...)`.
203
239
 
204
240
  # Run and get the execution
205
- execution = job.run(argString="-option value")
241
+ execution = job.run(as_execution=True, argString="-option value")
206
242
 
207
243
  # Bulk actions
208
244
  rd.jobs.bulk.enable_execution(["id1", "id2"])
@@ -360,7 +396,8 @@ ks = rd.key_storage
360
396
  # Create a secret (password)
361
397
  ks.create("integration/secret1", content="s3cr3t", content_type="application/x-rundeck-data-password")
362
398
  resources = ks.list() # root listing
363
- meta = ks.get_metadata("integration/secret1")
399
+ meta = ks.get("integration/secret1")
400
+ content = meta.content() # raw bytes
364
401
  ks.delete("integration/secret1")
365
402
 
366
403
  # AdHoc commands/scripts
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-rundeck"
3
- version = "0.3.0"
3
+ version = "1.0.0"
4
4
  description = "Python client for the Rundeck API (v14-v56)."
5
5
  authors = [
6
6
  {name = "Pascal Seckinger",email = "pascal.seckinger@protonmail.com"}
@@ -50,14 +50,16 @@ class RundeckObject(Generic[ResourceT]):
50
50
  def to_dict(self) -> dict[str, Any]:
51
51
  return dict(self._attrs)
52
52
 
53
- def refresh(self: ResourceT) -> ResourceT:
54
- """Recharge la ressource en interrogeant son manager."""
53
+ def refresh(self: ResourceT) -> None:
54
+ """
55
+ Recharge la ressource en interrogeant son manager.
56
+
57
+ """
55
58
  ident = self._attrs.get(self._id_attr)
56
59
  if ident is None:
57
60
  raise AttributeError(f"{self.__class__.__name__} has no identifier")
58
61
  fresh = self.manager.get(ident)
59
62
  self._attrs = fresh._attrs
60
- return self
61
63
 
62
64
  def pformat(self) -> str:
63
65
  """Retourne une représentation jolie du dictionnaire d'attributs."""
@@ -111,16 +111,15 @@ class Execution(RundeckObject):
111
111
  """
112
112
  return self._attrs.get("status") == "aborted"
113
113
 
114
- def refresh(self) -> "Execution":
114
+ def refresh(self) -> None:
115
115
  """
116
116
  Actualise les données de l'exécution
117
117
 
118
118
  Returns:
119
- Exécution actualisée
119
+ None.
120
120
  """
121
121
  updated = self.manager.get(self.id)
122
122
  self._attrs = updated._attrs
123
- return self
124
123
 
125
124
 
126
125
  class ExecutionManager(RundeckObjectManager):
@@ -22,22 +22,40 @@ class Job(RundeckObject):
22
22
  return f"{base}/{suffix.lstrip('/')}"
23
23
  return base
24
24
 
25
- def run(self, **kwargs: Any) -> Execution:
25
+ def run(
26
+ self, as_execution: bool = False, **kwargs: Any
27
+ ) -> Execution | dict[str, Any]:
26
28
  """
27
- Exécute le job et retourne l'exécution.
29
+ Exécute le job et retourne la réponse brute par défaut.
30
+
31
+ Args:
32
+ as_execution: True pour envelopper la réponse en Execution.
28
33
  """
29
34
  path = self._job_path("run")
30
35
  result = self.rd.http_post(path, json=kwargs or None)
31
- # Wrap avec le manager d'exécutions existant.
32
- return Execution(self.manager.rd.executions, result)
36
+ if as_execution:
37
+ # Wrap avec le manager d'exécutions existant.
38
+ return Execution(self.manager.rd.executions, result)
39
+ return result
33
40
 
34
- def retry(self, execution_id: str, **kwargs: Any) -> Execution:
41
+ def retry(
42
+ self,
43
+ execution_id: str,
44
+ as_execution: bool = False,
45
+ **kwargs: Any,
46
+ ) -> Execution | dict[str, Any]:
35
47
  """
36
48
  Relance une exécution (retry).
49
+
50
+ Args:
51
+ execution_id: ID de l'exécution à relancer.
52
+ as_execution: True pour envelopper la réponse en Execution.
37
53
  """
38
54
  path = self._job_path(f"retry/{execution_id}")
39
55
  result = self.rd.http_post(path, json=kwargs or None)
40
- return Execution(self.manager.rd.executions, result)
56
+ if as_execution:
57
+ return Execution(self.manager.rd.executions, result)
58
+ return result
41
59
 
42
60
  def delete(self) -> None:
43
61
  """Supprime le job courant."""
@@ -15,6 +15,28 @@ class StorageKey(RundeckObject):
15
15
  _id_attr = "path"
16
16
  _repr_attr = "name"
17
17
 
18
+ def _relative_path(self) -> str:
19
+ path = self.path
20
+ # API returns "keys/..." but endpoints expect the relative path.
21
+ if isinstance(path, str) and path.startswith("keys/"):
22
+ return path[len("keys/") :]
23
+ return path
24
+
25
+ def content(self, accept: str = "application/pgp-keys") -> bytes:
26
+ """Récupère le contenu d'une clé (ex: clé publique)."""
27
+ path = self.manager._file_path(self._relative_path())
28
+ headers = {"Accept": accept} if accept else None
29
+ response = self.manager.rd.http_get(path, headers=headers, raw=True)
30
+ return response.content
31
+
32
+ def update(self, content: bytes | str, content_type: str) -> dict[str, Any]:
33
+ """Met à jour le contenu de la clé et rafraîchit l'objet."""
34
+ updated = self.manager.update(
35
+ self._relative_path(), content=content, content_type=content_type
36
+ )
37
+ self._attrs = updated._attrs
38
+ return updated.to_dict()
39
+
18
40
 
19
41
  class StorageKeyManager(RundeckObjectManager[StorageKey]):
20
42
  """Manager pour les clés/ressources du storage."""
@@ -43,7 +65,7 @@ class StorageKeyManager(RundeckObjectManager[StorageKey]):
43
65
  resources = result.get("resources") or []
44
66
  return self._wrap_list(resources)
45
67
 
46
- def get_metadata(self, path: str) -> StorageKey:
68
+ def get(self, path: str) -> StorageKey:
47
69
  """Récupère les métadonnées d'une clé/fichier."""
48
70
  target = self._file_path(path)
49
71
  result = self.rd.http_get(target)
@@ -51,24 +73,23 @@ class StorageKeyManager(RundeckObjectManager[StorageKey]):
51
73
  return self._wrap(result)
52
74
  return self._wrap({"path": path, "raw": result})
53
75
 
54
- def get_content(self, path: str, accept: str = "application/pgp-keys") -> bytes:
55
- """Récupère le contenu d'une clé (ex: clé publique)."""
56
- target = self._file_path(path)
57
- headers = {"Accept": accept} if accept else None
58
- response = self.rd.http_get(target, headers=headers, raw=True)
59
- return response.content
60
-
61
- def create(self, path: str, content: bytes | str, content_type: str) -> Any:
76
+ def create(self, path: str, content: bytes | str, content_type: str) -> StorageKey:
62
77
  """Crée une nouvelle clé/fichier à l'emplacement donné."""
63
78
  target = self._file_path(path)
64
79
  headers = {"Content-Type": content_type, "Accept": "application/json"}
65
- return self.rd.http_post(target, data=content, headers=headers)
80
+ result = self.rd.http_post(target, data=content, headers=headers)
81
+ if isinstance(result, dict):
82
+ return self._wrap(result)
83
+ return self._wrap({"path": path, "raw": result})
66
84
 
67
- def update(self, path: str, content: bytes | str, content_type: str) -> Any:
85
+ def update(self, path: str, content: bytes | str, content_type: str) -> StorageKey:
68
86
  """Met à jour une clé/fichier existant."""
69
87
  target = self._file_path(path)
70
88
  headers = {"Content-Type": content_type, "Accept": "application/json"}
71
- return self.rd.http_put(target, data=content, headers=headers)
89
+ result = self.rd.http_put(target, data=content, headers=headers)
90
+ if isinstance(result, dict):
91
+ return self._wrap(result)
92
+ return self._wrap({"path": path, "raw": result})
72
93
 
73
94
  def delete(self, path: str) -> None:
74
95
  """Supprime une clé/fichier."""
@@ -34,6 +34,12 @@ class SystemACL(RundeckObject):
34
34
  _id_attr = "name"
35
35
  _repr_attr = "name"
36
36
 
37
+ def update(self, content: str) -> dict[str, Any]:
38
+ """Met à jour la policy ACL et rafraîchit l'objet."""
39
+ updated = self.manager.update(self.name, content)
40
+ self._attrs = updated._attrs
41
+ return updated.to_dict()
42
+
37
43
 
38
44
  class SystemACLManager(RundeckObjectManager[SystemACL]):
39
45
  """Sous-manager pour les endpoints ACL système."""
@@ -18,7 +18,7 @@ class User(RundeckObject):
18
18
  firstName: str | None = None,
19
19
  lastName: str | None = None,
20
20
  email: str | None = None,
21
- ) -> "User":
21
+ ) -> dict[str, Any]:
22
22
  """
23
23
  Met à jour les informations de l'utilisateur
24
24
 
@@ -28,7 +28,7 @@ class User(RundeckObject):
28
28
  email: Email
29
29
 
30
30
  Returns:
31
- Utilisateur mis à jour
31
+ Réponse brute de l'API.
32
32
  """
33
33
  data: dict[str, Any] = {}
34
34
  if firstName:
@@ -40,8 +40,9 @@ class User(RundeckObject):
40
40
 
41
41
  path = self.manager._build_path(f"info/{self.login}")
42
42
  result = self.manager.rd.http_post(path, json=data)
43
- self._attrs.update(result)
44
- return self
43
+ if isinstance(result, dict):
44
+ self._attrs.update(result)
45
+ return result
45
46
 
46
47
  def roles(self) -> list[str]:
47
48
  """
File without changes