kstlib 1.0.2__tar.gz → 1.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.
Files changed (170) hide show
  1. {kstlib-1.0.2/src/kstlib.egg-info → kstlib-1.1.0}/PKG-INFO +7 -2
  2. {kstlib-1.0.2 → kstlib-1.1.0}/pyproject.toml +24 -1
  3. kstlib-1.1.0/src/kstlib/cli/commands/rapi/list.py +261 -0
  4. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/kstlib.conf.yml +11 -0
  5. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/meta.py +1 -1
  6. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/rapi/__init__.py +8 -0
  7. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/rapi/client.py +51 -2
  8. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/rapi/config.py +563 -25
  9. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/rapi/exceptions.py +164 -0
  10. {kstlib-1.0.2 → kstlib-1.1.0/src/kstlib.egg-info}/PKG-INFO +7 -2
  11. kstlib-1.0.2/src/kstlib/cli/commands/rapi/list.py +0 -99
  12. {kstlib-1.0.2 → kstlib-1.1.0}/LICENSE.md +0 -0
  13. {kstlib-1.0.2 → kstlib-1.1.0}/MANIFEST.in +0 -0
  14. {kstlib-1.0.2 → kstlib-1.1.0}/README.md +0 -0
  15. {kstlib-1.0.2 → kstlib-1.1.0}/setup.cfg +0 -0
  16. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/__init__.py +0 -0
  17. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/__main__.py +0 -0
  18. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/__init__.py +0 -0
  19. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/channels/__init__.py +0 -0
  20. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/channels/base.py +0 -0
  21. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/channels/email.py +0 -0
  22. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/channels/slack.py +0 -0
  23. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/exceptions.py +0 -0
  24. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/manager.py +0 -0
  25. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/models.py +0 -0
  26. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/alerts/throttle.py +0 -0
  27. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/__init__.py +0 -0
  28. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/callback.py +0 -0
  29. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/config.py +0 -0
  30. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/errors.py +0 -0
  31. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/models.py +0 -0
  32. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/providers/__init__.py +0 -0
  33. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/providers/base.py +0 -0
  34. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/providers/oauth2.py +0 -0
  35. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/providers/oidc.py +0 -0
  36. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/session.py +0 -0
  37. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/auth/token.py +0 -0
  38. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cache/__init__.py +0 -0
  39. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cache/decorator.py +0 -0
  40. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cache/strategies.py +0 -0
  41. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/__init__.py +0 -0
  42. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/app.py +0 -0
  43. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/__init__.py +0 -0
  44. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/auth/__init__.py +0 -0
  45. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/auth/common.py +0 -0
  46. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/auth/login.py +0 -0
  47. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/auth/logout.py +0 -0
  48. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/auth/providers.py +0 -0
  49. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/auth/status.py +0 -0
  50. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/auth/token.py +0 -0
  51. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/auth/whoami.py +0 -0
  52. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/config.py +0 -0
  53. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/ops/__init__.py +0 -0
  54. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/ops/attach.py +0 -0
  55. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/ops/common.py +0 -0
  56. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/ops/list_sessions.py +0 -0
  57. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/ops/logs.py +0 -0
  58. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/ops/start.py +0 -0
  59. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/ops/status.py +0 -0
  60. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/ops/stop.py +0 -0
  61. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/rapi/__init__.py +0 -0
  62. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/rapi/call.py +0 -0
  63. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/rapi/show.py +0 -0
  64. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/secrets/__init__.py +0 -0
  65. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/secrets/common.py +0 -0
  66. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/secrets/decrypt.py +0 -0
  67. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/secrets/doctor.py +0 -0
  68. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/secrets/encrypt.py +0 -0
  69. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/commands/secrets/shred.py +0 -0
  70. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/cli/common.py +0 -0
  71. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/config/__init__.py +0 -0
  72. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/config/exceptions.py +0 -0
  73. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/config/export.py +0 -0
  74. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/config/loader.py +0 -0
  75. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/config/sops.py +0 -0
  76. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/db/__init__.py +0 -0
  77. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/db/aiosqlcipher.py +0 -0
  78. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/db/cipher.py +0 -0
  79. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/db/database.py +0 -0
  80. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/db/exceptions.py +0 -0
  81. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/db/pool.py +0 -0
  82. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/helpers/__init__.py +0 -0
  83. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/helpers/exceptions.py +0 -0
  84. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/helpers/time_trigger.py +0 -0
  85. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/limits.py +0 -0
  86. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/logging/__init__.py +0 -0
  87. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/logging/manager.py +0 -0
  88. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/__init__.py +0 -0
  89. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/builder.py +0 -0
  90. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/exceptions.py +0 -0
  91. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/filesystem.py +0 -0
  92. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/transport.py +0 -0
  93. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/transports/__init__.py +0 -0
  94. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/transports/gmail.py +0 -0
  95. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/transports/resend.py +0 -0
  96. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/mail/transports/smtp.py +0 -0
  97. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/metrics/__init__.py +0 -0
  98. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/metrics/decorators.py +0 -0
  99. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/metrics/exceptions.py +0 -0
  100. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/__init__.py +0 -0
  101. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/_styles.py +0 -0
  102. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/cell.py +0 -0
  103. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/config.py +0 -0
  104. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/delivery.py +0 -0
  105. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/exceptions.py +0 -0
  106. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/image.py +0 -0
  107. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/kv.py +0 -0
  108. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/list.py +0 -0
  109. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/metric.py +0 -0
  110. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/monitoring.py +0 -0
  111. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/renderer.py +0 -0
  112. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/service.py +0 -0
  113. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/table.py +0 -0
  114. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/monitoring/types.py +0 -0
  115. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ops/__init__.py +0 -0
  116. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ops/base.py +0 -0
  117. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ops/container.py +0 -0
  118. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ops/exceptions.py +0 -0
  119. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ops/manager.py +0 -0
  120. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ops/models.py +0 -0
  121. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ops/tmux.py +0 -0
  122. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ops/validators.py +0 -0
  123. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/py.typed +0 -0
  124. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/rapi/credentials.py +0 -0
  125. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/resilience/__init__.py +0 -0
  126. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/resilience/circuit_breaker.py +0 -0
  127. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/resilience/exceptions.py +0 -0
  128. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/resilience/heartbeat.py +0 -0
  129. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/resilience/rate_limiter.py +0 -0
  130. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/resilience/shutdown.py +0 -0
  131. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/resilience/watchdog.py +0 -0
  132. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/__init__.py +0 -0
  133. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/exceptions.py +0 -0
  134. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/models.py +0 -0
  135. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/providers/__init__.py +0 -0
  136. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/providers/base.py +0 -0
  137. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/providers/environment.py +0 -0
  138. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/providers/keyring.py +0 -0
  139. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/providers/kms.py +0 -0
  140. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/providers/kwargs.py +0 -0
  141. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/providers/sops.py +0 -0
  142. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/resolver.py +0 -0
  143. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secrets/sensitive.py +0 -0
  144. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secure/__init__.py +0 -0
  145. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secure/fs.py +0 -0
  146. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/secure/permissions.py +0 -0
  147. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ssl.py +0 -0
  148. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ui/__init__.py +0 -0
  149. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ui/exceptions.py +0 -0
  150. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ui/panels.py +0 -0
  151. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ui/spinner.py +0 -0
  152. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/ui/tables.py +0 -0
  153. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/__init__.py +0 -0
  154. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/dict.py +0 -0
  155. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/formatting.py +0 -0
  156. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/http_trace.py +0 -0
  157. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/lazy.py +0 -0
  158. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/secure_delete.py +0 -0
  159. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/serialization.py +0 -0
  160. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/text.py +0 -0
  161. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/utils/validators.py +0 -0
  162. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/websocket/__init__.py +0 -0
  163. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/websocket/exceptions.py +0 -0
  164. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/websocket/manager.py +0 -0
  165. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib/websocket/models.py +0 -0
  166. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib.egg-info/SOURCES.txt +0 -0
  167. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib.egg-info/dependency_links.txt +0 -0
  168. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib.egg-info/entry_points.txt +0 -0
  169. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib.egg-info/requires.txt +0 -0
  170. {kstlib-1.0.2 → kstlib-1.1.0}/src/kstlib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kstlib
3
- Version: 1.0.2
3
+ Version: 1.1.0
4
4
  Summary: Config-driven helpers for Python projects (dynamic config, secure secrets, preset logging, and more…)
5
5
  Author-email: Michel TRUONG <michel.truong@gmail.com>
6
6
  Maintainer-email: Michel TRUONG <michel.truong@gmail.com>
@@ -9,15 +9,20 @@ Project-URL: Homepage, https://github.com/KaminoU/kstlib
9
9
  Project-URL: Repository, https://github.com/KaminoU/kstlib
10
10
  Project-URL: Bug Tracker, https://github.com/KaminoU/kstlib/issues
11
11
  Project-URL: Changelog, https://github.com/KaminoU/kstlib/blob/main/CHANGELOG.md
12
- Keywords: kstlib
12
+ Keywords: kstlib,config,configuration,yaml,secrets,sops,logging,oauth2,oidc,rest-api,http-client,resilience,circuit-breaker,rate-limiter,websocket,alerts,toolkit
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
14
15
  Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
15
17
  Classifier: Programming Language :: Python :: 3
16
18
  Classifier: Programming Language :: Python :: 3.10
17
19
  Classifier: Programming Language :: Python :: 3.11
18
20
  Classifier: Programming Language :: Python :: 3.12
19
21
  Classifier: Programming Language :: Python :: 3.13
20
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Classifier: Typing :: Typed
21
26
  Requires-Python: >=3.10
22
27
  Description-Content-Type: text/markdown
23
28
  License-File: LICENSE.md
@@ -10,7 +10,25 @@ description = "Config-driven helpers for Python projects (dynamic config, secure
10
10
  readme = { file = "README.md", content-type = "text/markdown" }
11
11
  license = "MIT"
12
12
  license-files = ["LICENSE.md"]
13
- keywords = ["kstlib"]
13
+ keywords = [
14
+ "kstlib",
15
+ "config",
16
+ "configuration",
17
+ "yaml",
18
+ "secrets",
19
+ "sops",
20
+ "logging",
21
+ "oauth2",
22
+ "oidc",
23
+ "rest-api",
24
+ "http-client",
25
+ "resilience",
26
+ "circuit-breaker",
27
+ "rate-limiter",
28
+ "websocket",
29
+ "alerts",
30
+ "toolkit",
31
+ ]
14
32
  dynamic = ["version"]
15
33
  dependencies = [
16
34
  # --- Configuration & Serialization ---
@@ -43,13 +61,18 @@ dependencies = [
43
61
  ]
44
62
  classifiers = [
45
63
  "Development Status :: 5 - Production/Stable",
64
+ "Environment :: Console",
46
65
  "Intended Audience :: Developers",
66
+ "Operating System :: OS Independent",
47
67
  "Programming Language :: Python :: 3",
48
68
  "Programming Language :: Python :: 3.10",
49
69
  "Programming Language :: Python :: 3.11",
50
70
  "Programming Language :: Python :: 3.12",
51
71
  "Programming Language :: Python :: 3.13",
52
72
  "Programming Language :: Python :: 3.14",
73
+ "Topic :: Software Development :: Libraries :: Python Modules",
74
+ "Topic :: System :: Systems Administration",
75
+ "Typing :: Typed",
53
76
  ]
54
77
 
55
78
  requires-python = ">=3.10"
@@ -0,0 +1,261 @@
1
+ """List available API endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Annotated
6
+
7
+ import typer
8
+ from rich.table import Table
9
+
10
+ from kstlib.cli.common import console
11
+ from kstlib.rapi import load_rapi_config
12
+
13
+ if TYPE_CHECKING:
14
+ from kstlib.rapi.config import ApiConfig, EndpointConfig
15
+
16
+
17
+ def _matches_filter(
18
+ filter_terms: list[str],
19
+ ref: str,
20
+ method: str,
21
+ path: str,
22
+ description: str | None,
23
+ ) -> bool:
24
+ """Check if endpoint matches all filter terms (AND logic).
25
+
26
+ Args:
27
+ filter_terms: List of lowercase search terms.
28
+ ref: Endpoint reference (e.g., "github.repos").
29
+ method: HTTP method (e.g., "GET").
30
+ path: Endpoint path.
31
+ description: Optional endpoint description.
32
+
33
+ Returns:
34
+ True if all terms match any field.
35
+ """
36
+ searchable = f"{ref} {method} {path} {description or ''}".lower()
37
+ return all(term in searchable for term in filter_terms)
38
+
39
+
40
+ def _build_query_body_display(
41
+ ep_config: EndpointConfig,
42
+ method: str,
43
+ ) -> tuple[str, str]:
44
+ """Build query and body column displays.
45
+
46
+ Args:
47
+ ep_config: Endpoint configuration.
48
+ method: HTTP method.
49
+
50
+ Returns:
51
+ Tuple of (query_display, body_display).
52
+ """
53
+ query_display = f"[yellow]{len(ep_config.query)}[/]" if ep_config.query else "-"
54
+ body_display = "-"
55
+ if ep_config.body_template and method in ("POST", "PUT", "PATCH"):
56
+ body_display = f"[green]{len(ep_config.body_template)}[/]"
57
+ return query_display, body_display
58
+
59
+
60
+ def _build_compact_indicator(ep_config: EndpointConfig, method: str) -> str:
61
+ """Build compact param indicator for non-verbose mode.
62
+
63
+ Args:
64
+ ep_config: Endpoint configuration.
65
+ method: HTTP method.
66
+
67
+ Returns:
68
+ Formatted indicator string (e.g., " (4) (2)").
69
+ """
70
+ indicator = ""
71
+ if ep_config.query:
72
+ indicator += f" [yellow]({len(ep_config.query)})[/]"
73
+ if ep_config.body_template and method in ("POST", "PUT", "PATCH"):
74
+ indicator += f" [green]({len(ep_config.body_template)})[/]"
75
+ return indicator
76
+
77
+
78
+ def _add_endpoint_row(
79
+ table: Table,
80
+ ep_config: EndpointConfig,
81
+ api_name: str,
82
+ verbose: bool,
83
+ *,
84
+ short_desc: bool = False,
85
+ ) -> None:
86
+ """Add a single endpoint row to the table.
87
+
88
+ Args:
89
+ table: Rich table to add row to.
90
+ ep_config: Endpoint configuration.
91
+ api_name: Parent API name.
92
+ verbose: Whether to show verbose columns.
93
+ short_desc: Whether to truncate description (verbose mode only).
94
+ """
95
+ ref = f"{api_name}.{ep_config.name}"
96
+ method = ep_config.method.upper()
97
+ path_display = f"[dim]{ep_config.path}[/]"
98
+
99
+ if verbose:
100
+ query_display, body_display = _build_query_body_display(ep_config, method)
101
+ description = ep_config.description or ""
102
+ desc_display = (description[:40] + "...") if short_desc and len(description) > 43 else description
103
+ table.add_row(ref, method, path_display, query_display, body_display, desc_display)
104
+ else:
105
+ indicator = _build_compact_indicator(ep_config, method)
106
+ table.add_row(ref, method, path_display + indicator)
107
+
108
+
109
+ def _create_table(verbose: bool) -> Table:
110
+ """Create the endpoints table with appropriate columns.
111
+
112
+ Args:
113
+ verbose: Whether to include verbose columns.
114
+
115
+ Returns:
116
+ Configured Rich table.
117
+ """
118
+ table = Table(title="Available Endpoints", show_lines=False)
119
+ table.add_column("Reference", style="cyan")
120
+ table.add_column("Method", style="green", justify="center")
121
+ table.add_column("Path")
122
+
123
+ if verbose:
124
+ table.add_column("Query", justify="center")
125
+ table.add_column("Body", justify="center")
126
+ table.add_column("Description", style="dim")
127
+
128
+ return table
129
+
130
+
131
+ def _collect_matching_endpoints(
132
+ apis: dict[str, ApiConfig],
133
+ filter_terms: list[str],
134
+ ) -> list[tuple[str, EndpointConfig]]:
135
+ """Collect endpoints matching the filter.
136
+
137
+ Args:
138
+ apis: Dictionary of API configurations.
139
+ filter_terms: List of lowercase filter terms.
140
+
141
+ Returns:
142
+ List of (api_name, endpoint_config) tuples.
143
+ """
144
+ results = []
145
+ for api_name, api_config in sorted(apis.items()):
146
+ for ep_name, ep_config in sorted(api_config.endpoints.items()):
147
+ ref = f"{api_name}.{ep_name}"
148
+ method = ep_config.method.upper()
149
+ description = ep_config.description or ""
150
+
151
+ if filter_terms and not _matches_filter(filter_terms, ref, method, ep_config.path, description):
152
+ continue
153
+
154
+ results.append((api_name, ep_config))
155
+ return results
156
+
157
+
158
+ def list_endpoints(
159
+ api: Annotated[
160
+ str | None,
161
+ typer.Argument(help="Filter by API name (optional)."),
162
+ ] = None,
163
+ verbose: Annotated[
164
+ bool,
165
+ typer.Option(
166
+ "--verbose",
167
+ "-v",
168
+ help="Show additional details (method, description, query params).",
169
+ ),
170
+ ] = False,
171
+ filter_str: Annotated[
172
+ str | None,
173
+ typer.Option(
174
+ "--filter",
175
+ "-f",
176
+ help="Filter endpoints by keyword(s). Searches in ref, method, path, description.",
177
+ ),
178
+ ] = None,
179
+ short_desc: Annotated[
180
+ bool,
181
+ typer.Option(
182
+ "--short-desc",
183
+ help="Truncate descriptions to 40 chars (verbose mode only).",
184
+ ),
185
+ ] = False,
186
+ ) -> None:
187
+ """List all configured API endpoints.
188
+
189
+ Examples:
190
+ # List all endpoints
191
+ kstlib rapi list
192
+
193
+ # List endpoints for specific API
194
+ kstlib rapi list github
195
+
196
+ # Filter by keyword (searches everywhere)
197
+ kstlib rapi list --filter "delete"
198
+
199
+ # Multiple keywords (AND logic)
200
+ kstlib rapi list --filter "annotation GET"
201
+
202
+ # Combine API filter with keyword filter
203
+ kstlib rapi list annotations --filter "member"
204
+
205
+ # Verbose output with method, description, query params
206
+ kstlib rapi list -v
207
+
208
+ # Show details for specific endpoint (use 'rapi show')
209
+ kstlib rapi show annotations.create
210
+ """
211
+ try:
212
+ config_manager = load_rapi_config()
213
+ except Exception as e: # pylint: disable=broad-exception-caught
214
+ console.print(f"[red]Failed to load rapi config: {e}[/]")
215
+ raise typer.Exit(code=1) from e
216
+
217
+ apis = config_manager.apis
218
+
219
+ if not apis:
220
+ console.print("[yellow]No APIs configured in kstlib.conf.yml[/]")
221
+ console.print("[dim]Add APIs under 'rapi.api' section.[/]")
222
+ raise typer.Exit(code=0)
223
+
224
+ # Filter by API name if specified
225
+ if api:
226
+ if api not in apis:
227
+ console.print(f"[red]API '{api}' not found.[/]")
228
+ console.print(f"[dim]Available APIs: {', '.join(apis.keys())}[/]")
229
+ raise typer.Exit(code=1)
230
+ apis = {api: apis[api]}
231
+
232
+ # Parse filter terms
233
+ filter_terms = filter_str.lower().split() if filter_str else []
234
+
235
+ # Collect matching endpoints
236
+ matches = _collect_matching_endpoints(apis, filter_terms)
237
+
238
+ if not matches:
239
+ if filter_terms:
240
+ console.print(f"[yellow]No endpoints matching '{filter_str}'[/]")
241
+ else:
242
+ console.print("[yellow]No endpoints found.[/]")
243
+ raise typer.Exit(code=0)
244
+
245
+ # Build and populate table
246
+ table = _create_table(verbose)
247
+ for api_name, ep_config in matches:
248
+ _add_endpoint_row(table, ep_config, api_name, verbose, short_desc=short_desc)
249
+
250
+ console.print(table)
251
+
252
+ # Summary
253
+ total_apis = len(apis)
254
+ total_endpoints = sum(len(a.endpoints) for a in apis.values())
255
+ if filter_terms:
256
+ console.print(f"\n[dim]{len(matches)} matching / {total_endpoints} total endpoints[/]")
257
+ else:
258
+ console.print(f"\n[dim]{total_endpoints} endpoints across {total_apis} API(s)[/]")
259
+
260
+
261
+ __all__ = ["list_endpoints"]
@@ -623,6 +623,17 @@ rapi:
623
623
  retry_delay: 1.0 # Initial delay between retries (seconds)
624
624
  retry_backoff: 2.0 # Exponential backoff multiplier
625
625
 
626
+ # Safeguard configuration for dangerous HTTP methods
627
+ # Endpoints using these methods MUST define a safeguard string
628
+ # to prevent accidental destructive operations
629
+ safeguard:
630
+ # HTTP methods that require a safeguard to be defined on endpoints
631
+ # Default: DELETE and PUT (most destructive operations)
632
+ # Set to empty list [] to disable safeguard requirements
633
+ required_methods:
634
+ - DELETE
635
+ - PUT
636
+
626
637
  # Pretty-print settings for CLI output
627
638
  # Controls formatting of JSON and XML responses in terminal
628
639
  pretty_render:
@@ -39,7 +39,7 @@ __logo__ = (
39
39
  )
40
40
 
41
41
  __app_name__ = "kstlib"
42
- __version__ = "1.0.2"
42
+ __version__ = "1.1.0"
43
43
  __description__ = (
44
44
  "Config-driven helpers for Python projects (dynamic config, secure secrets, preset logging, and more…)"
45
45
  )
@@ -85,26 +85,32 @@ from kstlib.rapi.config import (
85
85
  EndpointConfig,
86
86
  HmacConfig,
87
87
  RapiConfigManager,
88
+ SafeguardConfig,
88
89
  load_rapi_config,
89
90
  )
90
91
  from kstlib.rapi.credentials import CredentialRecord, CredentialResolver
91
92
  from kstlib.rapi.exceptions import (
93
+ ConfirmationRequiredError,
92
94
  CredentialError,
93
95
  EndpointAmbiguousError,
94
96
  EndpointNotFoundError,
97
+ EnvVarError,
95
98
  RapiError,
96
99
  RequestError,
97
100
  ResponseTooLargeError,
101
+ SafeguardMissingError,
98
102
  )
99
103
 
100
104
  __all__ = [
101
105
  "ApiConfig",
106
+ "ConfirmationRequiredError",
102
107
  "CredentialError",
103
108
  "CredentialRecord",
104
109
  "CredentialResolver",
105
110
  "EndpointAmbiguousError",
106
111
  "EndpointConfig",
107
112
  "EndpointNotFoundError",
113
+ "EnvVarError",
108
114
  "HmacConfig",
109
115
  "RapiClient",
110
116
  "RapiConfigManager",
@@ -112,6 +118,8 @@ __all__ = [
112
118
  "RapiResponse",
113
119
  "RequestError",
114
120
  "ResponseTooLargeError",
121
+ "SafeguardConfig",
122
+ "SafeguardMissingError",
115
123
  "call",
116
124
  "call_async",
117
125
  "load_rapi_config",
@@ -27,6 +27,7 @@ from kstlib.rapi.config import (
27
27
  )
28
28
  from kstlib.rapi.credentials import CredentialRecord, CredentialResolver
29
29
  from kstlib.rapi.exceptions import (
30
+ ConfirmationRequiredError,
30
31
  RequestError,
31
32
  ResponseTooLargeError,
32
33
  )
@@ -45,6 +46,37 @@ def _log_trace(msg: str, *args: Any) -> None:
45
46
  log.log(TRACE_LEVEL, msg, *args)
46
47
 
47
48
 
49
+ def _validate_safeguard(
50
+ endpoint_config: EndpointConfig,
51
+ args: tuple[Any, ...],
52
+ kwargs: dict[str, Any],
53
+ confirm: str | None,
54
+ ) -> None:
55
+ """Validate safeguard confirmation for dangerous endpoints.
56
+
57
+ Args:
58
+ endpoint_config: Endpoint configuration.
59
+ args: Positional arguments for path/safeguard substitution.
60
+ kwargs: Keyword arguments for path/safeguard substitution.
61
+ confirm: Confirmation string provided by caller.
62
+
63
+ Raises:
64
+ ConfirmationRequiredError: If safeguard is required but confirm is missing or wrong.
65
+ """
66
+ if endpoint_config.safeguard is None:
67
+ return
68
+
69
+ expected = endpoint_config.build_safeguard(*args, **kwargs)
70
+ if expected is None:
71
+ return
72
+
73
+ if confirm is None:
74
+ raise ConfirmationRequiredError(endpoint_config.full_ref, expected=expected)
75
+
76
+ if confirm != expected:
77
+ raise ConfirmationRequiredError(endpoint_config.full_ref, expected=expected, actual=confirm)
78
+
79
+
48
80
  @dataclass
49
81
  class RapiResponse:
50
82
  """Response from an API call.
@@ -238,6 +270,7 @@ class RapiClient:
238
270
  body: Any = None,
239
271
  headers: Mapping[str, str] | None = None,
240
272
  timeout: float | None = None,
273
+ confirm: str | None = None,
241
274
  **kwargs: Any,
242
275
  ) -> RapiResponse:
243
276
  """Make a synchronous API call.
@@ -248,12 +281,14 @@ class RapiClient:
248
281
  body: Request body (dict for JSON, str for raw).
249
282
  headers: Runtime headers (override service/endpoint headers).
250
283
  timeout: Request timeout (uses config default if None).
284
+ confirm: Confirmation string for dangerous endpoints with safeguard.
251
285
  **kwargs: Keyword arguments for path parameters and query params.
252
286
 
253
287
  Returns:
254
288
  RapiResponse with parsed data.
255
289
 
256
290
  Raises:
291
+ ConfirmationRequiredError: If safeguard requires confirmation.
257
292
  RequestError: If request fails after retries.
258
293
  ResponseTooLargeError: If response exceeds max size.
259
294
 
@@ -262,6 +297,7 @@ class RapiClient:
262
297
  >>> client.call("httpbin.get_ip") # doctest: +SKIP
263
298
  >>> client.call("httpbin.delayed", 5) # doctest: +SKIP
264
299
  >>> client.call("httpbin.post_data", body={"key": "value"}) # doctest: +SKIP
300
+ >>> client.call("admin.delete_user", userId="123", confirm="DELETE USER 123") # doctest: +SKIP
265
301
  """
266
302
  log.debug("Calling endpoint: %s", endpoint_ref)
267
303
 
@@ -269,6 +305,9 @@ class RapiClient:
269
305
  api_config, endpoint_config = self._config_manager.resolve(endpoint_ref)
270
306
  _log_trace("Resolved to: %s", endpoint_config.full_ref)
271
307
 
308
+ # Validate safeguard before proceeding
309
+ _validate_safeguard(endpoint_config, args, kwargs, confirm)
310
+
272
311
  # Build request
273
312
  request = self._build_request(
274
313
  api_config,
@@ -290,6 +329,7 @@ class RapiClient:
290
329
  body: Any = None,
291
330
  headers: Mapping[str, str] | None = None,
292
331
  timeout: float | None = None,
332
+ confirm: str | None = None,
293
333
  **kwargs: Any,
294
334
  ) -> RapiResponse:
295
335
  """Make an asynchronous API call.
@@ -300,12 +340,14 @@ class RapiClient:
300
340
  body: Request body (dict for JSON, str for raw).
301
341
  headers: Runtime headers (override service/endpoint headers).
302
342
  timeout: Request timeout (uses config default if None).
343
+ confirm: Confirmation string for dangerous endpoints with safeguard.
303
344
  **kwargs: Keyword arguments for path parameters and query params.
304
345
 
305
346
  Returns:
306
347
  RapiResponse with parsed data.
307
348
 
308
349
  Raises:
350
+ ConfirmationRequiredError: If safeguard requires confirmation.
309
351
  RequestError: If request fails after retries.
310
352
  ResponseTooLargeError: If response exceeds max size.
311
353
  """
@@ -315,6 +357,9 @@ class RapiClient:
315
357
  api_config, endpoint_config = self._config_manager.resolve(endpoint_ref)
316
358
  _log_trace("Resolved to: %s", endpoint_config.full_ref)
317
359
 
360
+ # Validate safeguard before proceeding
361
+ _validate_safeguard(endpoint_config, args, kwargs, confirm)
362
+
318
363
  # Build request
319
364
  request = self._build_request(
320
365
  api_config,
@@ -814,6 +859,7 @@ def call(
814
859
  *args: Any,
815
860
  body: Any = None,
816
861
  headers: Mapping[str, str] | None = None,
862
+ confirm: str | None = None,
817
863
  **kwargs: Any,
818
864
  ) -> RapiResponse:
819
865
  """Convenience function for quick API calls.
@@ -825,6 +871,7 @@ def call(
825
871
  *args: Positional path parameters.
826
872
  body: Request body.
827
873
  headers: Runtime headers.
874
+ confirm: Confirmation string for dangerous endpoints with safeguard.
828
875
  **kwargs: Keyword parameters.
829
876
 
830
877
  Returns:
@@ -835,7 +882,7 @@ def call(
835
882
  >>> response = call("httpbin.get_ip") # doctest: +SKIP
836
883
  """
837
884
  client = RapiClient()
838
- return client.call(endpoint_ref, *args, body=body, headers=headers, **kwargs)
885
+ return client.call(endpoint_ref, *args, body=body, headers=headers, confirm=confirm, **kwargs)
839
886
 
840
887
 
841
888
  async def call_async(
@@ -843,6 +890,7 @@ async def call_async(
843
890
  *args: Any,
844
891
  body: Any = None,
845
892
  headers: Mapping[str, str] | None = None,
893
+ confirm: str | None = None,
846
894
  **kwargs: Any,
847
895
  ) -> RapiResponse:
848
896
  """Convenience function for async API calls.
@@ -854,6 +902,7 @@ async def call_async(
854
902
  *args: Positional path parameters.
855
903
  body: Request body.
856
904
  headers: Runtime headers.
905
+ confirm: Confirmation string for dangerous endpoints with safeguard.
857
906
  **kwargs: Keyword parameters.
858
907
 
859
908
  Returns:
@@ -864,7 +913,7 @@ async def call_async(
864
913
  >>> response = await call_async("httpbin.get_ip") # doctest: +SKIP
865
914
  """
866
915
  client = RapiClient()
867
- return await client.call_async(endpoint_ref, *args, body=body, headers=headers, **kwargs)
916
+ return await client.call_async(endpoint_ref, *args, body=body, headers=headers, confirm=confirm, **kwargs)
868
917
 
869
918
 
870
919
  __all__ = [