python-rundeck 0.2.0__py3-none-any.whl → 0.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-rundeck
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Python client for the Rundeck API (v14-v56).
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -38,44 +38,46 @@ Description-Content-Type: text/markdown
38
38
 
39
39
  python-rundeck
40
40
  ===============
41
+ ![PyPI Version](https://img.shields.io/pypi/v/python-rundeck)
42
+ ![Python Versions](https://img.shields.io/pypi/pyversions/python-rundeck)
43
+ ![License](https://img.shields.io/pypi/l/python-rundeck)
44
+ ![Downloads](https://static.pepy.tech/badge/python-rundeck)
41
45
 
42
- Client Python pour l’API Rundeck (v14v56), inspiré de l’architecture de python-gitlab. Fournit des managers typés pour les ressources clés (projects, jobs, executions, tokens, users, système, configuration) et SCM (import/export).
46
+ Python client for the Rundeck API (v14-v56), inspired by the python-gitlab architecture. Provides typed managers for key resources (projects, jobs, executions, tokens, users, system, configuration) and SCM (import/export).
43
47
 
44
- Sommaire
48
+ Contents
45
49
  --------
46
50
  - Installation
47
- - Démarrage rapide
51
+ - Quick start
48
52
  - Configuration
49
- - Ressources disponibles
50
- - Exemples par ressource
51
- - Gestion des erreurs
52
- - Développement et tests
53
+ - Available resources
54
+ - Examples by resource
55
+ - Error handling
56
+ - Development and testing
53
57
 
54
58
  Installation
55
59
  ------------
56
- Prerequis : Python 3.11+. Le projet utilise Poetry.
60
+ Prerequisites: Python 3.11+. Install from PyPI.
57
61
 
58
62
  ```bash
59
- poetry install
60
- # ou en editable avec pip
61
- pip install -e .
63
+ pip install python-rundeck
62
64
  ```
63
65
 
64
- Démarrage rapide
65
- ----------------
66
+ Quick start
67
+ -----------
66
68
  ```python
67
69
  from rundeck.client import Rundeck
68
70
 
69
71
  rd = Rundeck(url="https://rundeck.example.com", token="MY_TOKEN", api_version=56)
70
- # Auth par mot de passe (session cookie) si pas de token
72
+ # Password auth (session cookie) if no token
71
73
  rd = Rundeck(url="https://rundeck.example.com", username="admin", password="admin", api_version=56)
72
74
 
73
- # Lister les projets
75
+ # List projects
74
76
  projects = rd.projects.list()
75
77
  for p in projects:
76
78
  print(p.name)
77
79
 
78
- # Récupérer un projet et lancer un job
80
+ # Get a project and run a job
79
81
  project = rd.projects.get("demo")
80
82
  jobs = project.jobs.list()
81
83
  execu = jobs[0].run()
@@ -84,64 +86,64 @@ print(execu.id)
84
86
 
85
87
  Configuration
86
88
  -------------
87
- La configuration suit un modèle en cascade (args > env > fichiers > défauts) via `RundeckConfig`.
88
-
89
- Paramètres principaux :
90
- - `url` : URL Rundeck (ex. `https://rundeck.example.com`)
91
- - `token` : Token API (en-tête `X-Rundeck-Auth-Token`)
92
- - `username` / `password` : Authentification par session (j_security_check) si aucun token n'est fourni.
93
- - `api_version` : Version d’API (ex. `56`)
94
- - `timeout` : Timeout des requêtes (float, secondes)
95
- - `ssl_verify` : Vérification TLS (bool ou chemin CA)
96
-
97
- Fichiers de config : si besoin, passez `config_files` ou `Rundeck.from_config(config_section=...)`.
98
- Variables d'env utiles (config client) :
99
- - `RUNDECK_URL` : URL de base
100
- - `RUNDECK_TOKEN` : token API (en-tête `X-Rundeck-Auth-Token`)
101
- - `RUNDECK_USERNAME` / `RUNDECK_PASSWORD` : auth session (si pas de token)
102
- - `RUNDECK_API_VERSION` : version d'API (ex: 56)
103
- - `RUNDECK_TIMEOUT` : timeout des requêtes (secondes)
104
- - `RUNDECK_SSL_VERIFY` : vérification TLS (bool ou chemin CA)
105
- - `RUNDECK_USER_AGENT` : User-Agent HTTP
106
-
107
- Ressources disponibles
108
- ----------------------
109
- - `projects` (`ProjectManager`) : CRUD projets, export/import de jobs, config projet, archive (export/import).
110
- - `jobs` (`JobManager`) : liste, get, suppressions, actions bulk, exécution.
111
- - `executions` (`ExecutionManager`) : liste/filtre, running, get/delete, query avancée.
112
- - `tokens` (`TokenManager`) : liste, get, create, delete.
113
- - `users` (`UserManager`) : opérations utilisateurs (selon implémentation courante).
114
- - `metrics` (`MetricsManager`) : endpoints `/metrics` (list/data/healthcheck/ping).
115
- - `plugins` (`PluginManager`) : liste des plugins installés (`/plugin/list`).
116
- - `webhooks` (`WebhookEventManager` + `ProjectWebhookManager`) : gestion des webhooks projet et envoi via token.
117
- - `key_storage` (`StorageKeyManager`) : gestion du stockage des clés `/storage/keys`.
118
- - `adhoc` (`AdhocManager`, via `project.adhoc`) : exécution de commandes/scripts AdHoc.
119
- - `system` (`SystemManager`) : info système, exécutions enable/disable, logstorage, ACL.
120
- - `config_management` (`ConfigManagementManager`) : configuration globale `/config`.
121
- - `scm` (via `project.scm` et `job.scm`) : plugins import/export, setup, enable/disable, statut, actions (commit/import/export...).
122
-
123
- Exemples par ressource (complets)
124
- ---------------------------------
125
- Projets
89
+ Configuration follows a cascading model (args > env > files > defaults) via `RundeckConfig`.
90
+
91
+ Main parameters:
92
+ - `url`: Rundeck URL (e.g. `https://rundeck.example.com`)
93
+ - `token`: API token (header `X-Rundeck-Auth-Token`)
94
+ - `username` / `password`: Session authentication (j_security_check) if no token is provided.
95
+ - `api_version`: API version (e.g. `56`)
96
+ - `timeout`: Request timeout (float, seconds)
97
+ - `ssl_verify`: TLS verification (bool or CA path)
98
+
99
+ Config files: if needed, pass `config_files` or `Rundeck.from_config(config_section=...)`.
100
+ Useful env vars (client config):
101
+ - `RUNDECK_URL`: Base URL
102
+ - `RUNDECK_TOKEN`: API token (header `X-Rundeck-Auth-Token`)
103
+ - `RUNDECK_USERNAME` / `RUNDECK_PASSWORD`: Session auth (if no token)
104
+ - `RUNDECK_API_VERSION`: API version (e.g. 56)
105
+ - `RUNDECK_TIMEOUT`: Request timeout (seconds)
106
+ - `RUNDECK_SSL_VERIFY`: TLS verification (bool or CA path)
107
+ - `RUNDECK_USER_AGENT`: HTTP User-Agent
108
+
109
+ Available resources
110
+ -------------------
111
+ - `projects` (`ProjectManager`): CRUD projects, job export/import, project config, archive (export/import).
112
+ - `jobs` (`JobManager`): list, get, deletions, bulk actions, execution.
113
+ - `executions` (`ExecutionManager`): list/filter, running, get/delete, advanced query.
114
+ - `tokens` (`TokenManager`): list, get, create, delete.
115
+ - `users` (`UserManager`): user operations (per current implementation).
116
+ - `metrics` (`MetricsManager`): `/metrics` endpoints (list/data/healthcheck/ping).
117
+ - `plugins` (`PluginManager`): list installed plugins (`/plugin/list`).
118
+ - `webhooks` (`WebhookEventManager` + `ProjectWebhookManager`): project webhook management and sending via token.
119
+ - `key_storage` (`StorageKeyManager`): key storage management `/storage/keys`.
120
+ - `adhoc` (`AdhocManager`, via `project.adhoc`): run AdHoc commands/scripts.
121
+ - `system` (`SystemManager`): system info, executions enable/disable, logstorage, ACL.
122
+ - `config_management` (`ConfigManagementManager`): global configuration `/config`.
123
+ - `scm` (via `project.scm` and `job.scm`): import/export plugins, setup, enable/disable, status, actions (commit/import/export...).
124
+
125
+ Examples by resource (complete)
126
+ -------------------------------
127
+ Projects
126
128
  ```python
127
- # CRUD projet
129
+ # CRUD project
128
130
  p = rd.projects.create("demo")
129
131
  p = rd.projects.get("demo")
130
132
  rd.projects.delete("demo")
131
133
  projects = rd.projects.list()
132
134
 
133
- # Export / import de jobs d'un projet (via le manager de jobs)
135
+ # Export / import jobs from a project (via job manager)
134
136
  project.jobs.export(format="json", idlist="id1,id2", groupPath="group/sub")
135
137
  project.jobs.import_jobs(content=open("jobs.json").read(), format="json", dupeOption="update", uuidOption="remove")
136
138
 
137
- # Export / import du projet (archive ZIP)
139
+ # Project export / import (ZIP archive)
138
140
  archive = project.archive
139
- resp = archive.export(export_all=False, export_webhooks=True) # Response brute (zip)
141
+ resp = archive.export(export_all=False, export_webhooks=True) # Raw response (zip)
140
142
  token_info = archive.export_async(exportAll=True)
141
143
  status = archive.export_status(token_info.get("token", ""))
142
144
  zip_resp = archive.export_download(token_info.get("token", ""))
143
145
 
144
- # Import d'archive (synchrone ou async)
146
+ # Archive import (sync or async)
145
147
  archive.import_archive(
146
148
  content=open("project-export.zip", "rb").read(),
147
149
  jobUuidOption="preserve",
@@ -151,16 +153,16 @@ archive.import_archive(
151
153
  archive.import_archive(content=open("project-export.zip", "rb").read(), async_import=True)
152
154
  archive.import_status()
153
155
 
154
- # Readme / MOTD du projet
155
- project.readme.get_readme() # texte par défaut
156
+ # Project README / MOTD
157
+ project.readme.get_readme() # default text
156
158
  project.readme.get_readme(accept="application/json")
157
- project.readme.update_readme("Nouveau contenu", content_type="text/plain")
159
+ project.readme.update_readme("New content", content_type="text/plain")
158
160
  project.readme.delete_readme()
159
161
  project.readme.get_motd()
160
- project.readme.update_motd("Message du jour", content_type="text/plain")
162
+ project.readme.update_motd("Message of the day", content_type="text/plain")
161
163
  project.readme.delete_motd()
162
164
 
163
- # Config projet (clé/valeur)
165
+ # Project config (key/value)
164
166
  conf = p.config.get()
165
167
  p.config.keys.get("project.label")
166
168
  p.config.keys.set("project.label", "Demo")
@@ -168,27 +170,27 @@ p.config.keys.update({"project.description": "Sample"})
168
170
  p.config.replace({"project.label": "Demo", "project.description": "Sample"})
169
171
  p.config.keys.delete("project.label")
170
172
 
171
- # SCM import/export (sur un projet)
172
- scm_import = project.scm.import_ # ou getattr(project.scm, "import")
173
+ # SCM import/export (on a project)
174
+ scm_import = project.scm.import_ # or getattr(project.scm, "import")
173
175
  scm_export = project.scm.export
174
176
 
175
- # Découverte des plugins
177
+ # Plugin discovery
176
178
  scm_import.plugins.list()
177
179
  scm_export.plugins.list()
178
180
 
179
- # Champs d'entrée pour un plugin et setup (plugin_type explicite)
181
+ # Input fields for a plugin and setup (explicit plugin_type)
180
182
  fields = scm_import.plugins.input_fields("git-import")
181
183
  scm_import.config.setup("git-import", {"url": "ssh://git@example.com/repo.git", "dir": "/tmp/repo"})
182
184
 
183
- # Activer/désactiver un plugin
185
+ # Enable/disable a plugin
184
186
  scm_import.config.enable("git-import")
185
187
  scm_export.config.disable("custom-export")
186
188
 
187
- # Statut/config SCM
189
+ # SCM status/config
188
190
  import_status = scm_import.actions.status()
189
191
  export_conf = scm_export.config.get()
190
192
 
191
- # Actions SCM côté projet (ex: commit/pull/push selon plugin)
193
+ # Project-side SCM actions (e.g., commit/pull/push depending on plugin)
192
194
  action_fields = scm_export.actions.input_fields("commit")
193
195
  scm_export.actions.perform(
194
196
  "commit",
@@ -201,14 +203,14 @@ scm_export.actions.perform(
201
203
 
202
204
  Jobs
203
205
  ```python
204
- # Lister les jobs d’un projet
206
+ # List jobs for a project
205
207
  jobs = rd.jobs.list(project="demo", groupPath="ops")
206
208
 
207
- # Depuis un projet parenté
209
+ # From a parent project
208
210
  project = rd.projects.get("demo")
209
211
  jobs = project.jobs.list()
210
212
 
211
- # Accès direct à un job
213
+ # Direct access to a job
212
214
  job = rd.jobs.get("job-id")
213
215
  job.delete()
214
216
  job.definition(format="yaml")
@@ -223,7 +225,7 @@ tags = job.tags()
223
225
  workflow = job.workflow()
224
226
  forecast = job.forecast(time="2024-05-01T10:00:00Z", max=5)
225
227
 
226
- # Exporter/importer des jobs via le manager (paramètre project ou parent)
228
+ # Export/import jobs via manager (project parameter or parent)
227
229
  rd.jobs.export(project="demo", format="xml", idlist="id1,id2", groupPath="group")
228
230
  rd.jobs.import_jobs(
229
231
  project="demo",
@@ -231,30 +233,30 @@ rd.jobs.import_jobs(
231
233
  fileformat="xml",
232
234
  dupeOption="update",
233
235
  )
234
- # Ou via un projet parenté
236
+ # Or via a parent project
235
237
  project.jobs.export(format="json")
236
238
  project.jobs.import_jobs(content=open("jobs.json", "rb").read(), fileformat="json")
237
239
 
238
- Note: l'import reste exposé sous le nom `import_jobs(...)` (le mot-clé Python empêche un appel direct à `.import`). Si vous préférez l'alias, utilisez `getattr(rd.jobs, "import")(...)`.
240
+ 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")(...)`.
239
241
 
240
- # Exécuter et récupérer l'exécution
242
+ # Run and get the execution
241
243
  execution = job.run(argString="-option value")
242
244
 
243
- # Actions bulk
245
+ # Bulk actions
244
246
  rd.jobs.bulk.enable_execution(["id1", "id2"])
245
247
  rd.jobs.bulk.disable_execution(["id1", "id2"])
246
248
  rd.jobs.bulk.delete(["id1", "id2"])
247
249
  rd.jobs.bulk.enable_schedule(["id1", "id2"])
248
250
  rd.jobs.bulk.disable_schedule(["id1", "id2"])
249
251
 
250
- # Upload de fichiers d'option et fichiers uploadés
252
+ # Upload option files and uploaded files
251
253
  job.upload_option_file("csvfile", open("data.csv", "rb").read(), file_name="data.csv")
252
254
  job.list_uploaded_files(max=20)
253
255
  rd.jobs.get_uploaded_file_info("file-id")
254
256
 
255
- # SCM import/export sur un job
257
+ # SCM import/export on a job
256
258
  job_scm_export = job.scm.export
257
- job_scm_import = job.scm.import_ # ou getattr(job.scm, "import")
259
+ job_scm_import = job.scm.import_ # or getattr(job.scm, "import")
258
260
 
259
261
  job_scm_export.status()
260
262
  job_scm_export.diff()
@@ -264,7 +266,7 @@ job_scm_import.status()
264
266
  job_scm_import.input_fields("pull")
265
267
  job_scm_import.perform("pull", input_values={"message": "Update from repo"})
266
268
 
267
- # Ressources d'un projet
269
+ # Project resources
268
270
  resources = project.resources.list(format="json", groupPath="ops")
269
271
  node = project.resources.get("node1")
270
272
  sources = project.sources.list()
@@ -278,18 +280,18 @@ project.acl.update("policy.aclpolicy", content="...yaml...")
278
280
  project.acl.delete("policy.aclpolicy")
279
281
  ```
280
282
 
281
- Exécutions
283
+ Executions
282
284
  ```python
283
- # Liste simple ou paginée
285
+ # Simple or paginated list
284
286
  execs = rd.executions.list(project="demo", status="running", max=50, offset=0)
285
- running = rd.executions.running(project="demo") # ou "*" pour tous
287
+ running = rd.executions.running(project="demo") # or "*" for all
286
288
 
287
- # Détails / suppression
289
+ # Details / deletion
288
290
  e = rd.executions.get("123")
289
291
  rd.executions.delete("123")
290
292
  rd.executions.bulk_delete(["123", "124"])
291
293
 
292
- # Requête avancée (query)
294
+ # Advanced query
293
295
  advanced = rd.executions.query(
294
296
  project="demo",
295
297
  statusFilter="failed",
@@ -299,12 +301,12 @@ advanced = rd.executions.query(
299
301
  max=100,
300
302
  )
301
303
 
302
- # Méthodes sur Execution
304
+ # Execution methods
303
305
  e.abort(asUser="admin")
304
306
  output = e.get_output(offset=0, maxlines=100)
305
307
  state = e.get_state()
306
308
  is_running = e.is_running()
307
- e.refresh() # recharge les données
309
+ e.refresh() # reload data
308
310
  ```
309
311
 
310
312
  Tokens
@@ -316,20 +318,20 @@ new_token = rd.tokens.create(user="alice", roles=["admin"], duration="90d", name
316
318
  rd.tokens.delete(new_token.id)
317
319
  ```
318
320
 
319
- Utilisateurs
321
+ Users
320
322
  ```python
321
323
  users = rd.users.list()
322
324
  me = rd.users.get_current()
323
325
  u = rd.users.get("bob")
324
326
  roles = rd.users.current_roles()
325
327
 
326
- # Mise à jour via manager ou objet
328
+ # Update via manager or object
327
329
  u = rd.users.update("bob", firstName="Bob", lastName="Builder", email="bob@example.com")
328
- u.roles() # via l'objet
329
- u.update(email="new@example.com") # met à jour et rafraîchit l'objet
330
+ u.roles() # via the object
331
+ u.update(email="new@example.com") # updates and refreshes the object
330
332
  ```
331
333
 
332
- Système
334
+ System
333
335
  ```python
334
336
  system = rd.system
335
337
  info = system.info()
@@ -339,7 +341,7 @@ system.logstorage.info()
339
341
  system.logstorage.incomplete(max=50, offset=0)
340
342
  system.logstorage.incomplete_resume()
341
343
 
342
- # Mode exécution (sous-manager)
344
+ # Execution mode (sub-manager)
343
345
  system.executions.enable()
344
346
  system.executions.disable()
345
347
  system.executions.status()
@@ -356,7 +358,7 @@ rd.scheduler.takeover(all_servers=True)
356
358
  rd.scheduler.takeover(server_uuid="uuid-123", project="demo", job_id="job-1")
357
359
  ```
358
360
 
359
- Métriques
361
+ Metrics
360
362
  ```python
361
363
  metrics = rd.metrics
362
364
  metrics.list()
@@ -364,16 +366,16 @@ metrics.data()
364
366
  metrics.healthcheck()
365
367
  metrics.ping()
366
368
 
367
- # Plugins installés
369
+ # Installed plugins
368
370
  plugins = rd.plugins.list()
369
371
  for plugin in plugins:
370
372
  print(plugin.name, plugin.service)
371
373
 
372
- # Détail d'un plugin
374
+ # Plugin details
373
375
  first = plugins[0]
374
376
  detail = rd.plugins.detail(first.service, first.name)
375
377
 
376
- # Webhooks projet et envoi
378
+ # Project webhooks and send
377
379
  project = rd.projects.get("demo")
378
380
  wh = project.webhooks
379
381
  wh.create(
@@ -393,9 +395,9 @@ wh.delete(first_hook.id)
393
395
 
394
396
  # Key storage (/storage/keys)
395
397
  ks = rd.key_storage
396
- # Créer un secret (password)
398
+ # Create a secret (password)
397
399
  ks.create("integration/secret1", content="s3cr3t", content_type="application/x-rundeck-data-password")
398
- resources = ks.list() # liste racine
400
+ resources = ks.list() # root listing
399
401
  meta = ks.get_metadata("integration/secret1")
400
402
  ks.delete("integration/secret1")
401
403
 
@@ -403,17 +405,17 @@ ks.delete("integration/secret1")
403
405
  project = rd.projects.get("demo")
404
406
  exec1 = project.adhoc.run_command("echo 'hello world'")
405
407
  exec2 = project.adhoc.run_script("echo 'from script'")
406
- # exec2.id pour suivre l'exécution
407
- # Mode stub (sans refresh immédiat) puis refresh manuel
408
+ # exec2.id to track execution
409
+ # Stub mode (no immediate refresh) then manual refresh
408
410
  exec_stub = project.adhoc.run_command("echo stub", refresh=False)
409
- exec_stub.refresh() # charge l'objet complet
411
+ exec_stub.refresh() # load the full object
410
412
  # Script via multipart (upload)
411
413
  exec3 = project.adhoc.run_script(
412
414
  script_file=("hello.sh", "echo multipart", "text/plain"),
413
415
  refresh=False,
414
416
  )
415
417
 
416
- # Features système
418
+ # System features
417
419
  features = rd.features.list()
418
420
  if features:
419
421
  first = features[0]
@@ -421,7 +423,7 @@ if features:
421
423
  print(first.name, status.enabled)
422
424
  ```
423
425
 
424
- Configuration globale `/config`
426
+ Global configuration `/config`
425
427
  ```python
426
428
  cfg = rd.config_management
427
429
  all_configs = cfg.list()
@@ -433,27 +435,28 @@ cfg.restart()
433
435
 
434
436
  Pagination
435
437
  ----------
436
- Les managers héritent de `RundeckObjectManager.iter(...)` (offset/max). Exemple :
438
+ Managers inherit `RundeckObjectManager.iter(...)` (offset/max). Example:
437
439
  ```python
438
440
  for job in rd.jobs.iter(project="demo", page_size=100):
439
441
  print(job.id)
440
442
  ```
441
443
 
442
- Gestion des erreurs
443
- -------------------
444
- Les erreurs HTTP passent par `raise_for_status` et lèvent des exceptions dédiées (ex. `RundeckAuthenticationError`, `RundeckNotFoundError`, `RundeckValidationError`, `RundeckConflictError`, `RundeckServerError`). Gérez-les avec un bloc `try/except` autour des appels client.
445
-
446
- Développement et tests
447
- ----------------------
448
- - Formatage/Lint : `black src/rundeck tests/`, `ruff check src/rundeck tests/`
449
- - Typage : `mypy src/rundeck/`
450
- - Tests : `pytest`
451
- - Tests d'intégration locaux : `scripts/run-integration.sh` (démarre docker compose à la racine, exporte par défaut `RUNDECK_URL=http://localhost:4440`, `RUNDECK_TOKEN=adminToken`, `RUNDECK_API_VERSION=56`, attend le healthcheck, lance `poetry run pytest -m integration`). Vous pouvez surcharger les variables d'environnement avant l'appel. Ajoutez `KEEP_STACK=1` pour ne pas arrêter l'instance après les tests. Script de teardown dédié : `scripts/stop-integration.sh`.
452
-
453
- Structure du code
454
- -----------------
455
- - `src/rundeck/base.py` : objets/manager génériques, helpers `_build_path`, pagination, CRUD.
456
- - `src/rundeck/client.py` : client HTTP, méthodes `http_get/post/put/delete/list`.
457
- - `src/rundeck/v1/objects/` : managers et objets métiers (projects, jobs, executions, system, tokens, users, config_management).
458
- - Schéma managers/objets : on applique le pattern manager → objet partout où l’API expose une ressource identifiable (jobs, projects, users, tokens, executions, etc.). Pour les endpoints purement globaux ou utilitaires sans ressource (ex. `/system` et ses sous-domaines, `/config`, `/metrics`), on utilise des sous-managers dédiés plutôt que de forcer un objet artificiel.
444
+ Error handling
445
+ --------------
446
+ HTTP errors go through `raise_for_status` and raise dedicated exceptions (e.g., `RundeckAuthenticationError`, `RundeckNotFoundError`, `RundeckValidationError`, `RundeckConflictError`, `RundeckServerError`). Handle them with a `try/except` block around client calls.
447
+
448
+ Development and testing
449
+ -----------------------
450
+ - Development setup (contributors): clone the repo and run `poetry install`.
451
+ - Formatting/Lint: `black src/rundeck tests/`, `ruff check src/rundeck tests/`
452
+ - Typing: `mypy src/rundeck/`
453
+ - Tests: `pytest`
454
+ - Local integration tests: `scripts/run-integration.sh` (starts docker compose at repo root, exports by default `RUNDECK_URL=http://localhost:4440`, `RUNDECK_TOKEN=adminToken`, `RUNDECK_API_VERSION=56`, waits for the healthcheck, runs `poetry run pytest -m integration`). You can override env vars before running. Add `KEEP_STACK=1` to keep the instance running after tests. Dedicated teardown script: `scripts/stop-integration.sh`.
455
+
456
+ Code structure
457
+ --------------
458
+ - `src/rundeck/base.py`: generic objects/managers, `_build_path` helpers, pagination, CRUD.
459
+ - `src/rundeck/client.py`: HTTP client, `http_get/post/put/delete/list` methods.
460
+ - `src/rundeck/v1/objects/`: domain managers and objects (projects, jobs, executions, system, tokens, users, config_management).
461
+ - Managers/objects schema: we apply the manager -> object pattern wherever the API exposes an identifiable resource (jobs, projects, users, tokens, executions, etc.). For purely global or utility endpoints without a resource (e.g., `/system` and its subdomains, `/config`, `/metrics`), we use dedicated sub-managers rather than forcing an artificial object.
459
462
 
@@ -22,7 +22,7 @@ rundeck/v1/objects/system.py,sha256=wTfIxs4zINXxJF3zaEckB75sTFeeUq31O864y-B_pdo,
22
22
  rundeck/v1/objects/tokens.py,sha256=OWnfs1QUTpJbsaKf7r0Y7w3VYxxGZxBDead8JAoPKck,2373
23
23
  rundeck/v1/objects/users.py,sha256=N7WjC2UqXrmd0PurI317lua5wMSZN-C1YfdjjPWpqgA,3278
24
24
  rundeck/v1/objects/webhooks.py,sha256=F_HzNvlBRPvS_viu2reCRc7ECqRSUhp5RfD_G3r-ZZk,3358
25
- python_rundeck-0.2.0.dist-info/METADATA,sha256=GeQm-Zd0snpKyx4mnG7sIwYlXQNj4GAnHYeI2YScFgE,16520
26
- python_rundeck-0.2.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
27
- python_rundeck-0.2.0.dist-info/licenses/LICENSE,sha256=3X-BjvCzow8iLfLMykDaEAXjE9JvDrEHPkH9UK7MMMA,1073
28
- python_rundeck-0.2.0.dist-info/RECORD,,
25
+ python_rundeck-0.3.0.dist-info/METADATA,sha256=R2kNIAOkP26epzMRnUSZiRIwb7fvk4ayS0dmRwXM-Ug,16283
26
+ python_rundeck-0.3.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
27
+ python_rundeck-0.3.0.dist-info/licenses/LICENSE,sha256=3X-BjvCzow8iLfLMykDaEAXjE9JvDrEHPkH9UK7MMMA,1073
28
+ python_rundeck-0.3.0.dist-info/RECORD,,