dirigent-server 0.16.6__tar.gz → 0.17.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 (180) hide show
  1. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/PKG-INFO +5 -5
  2. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/pyproject.toml +5 -5
  3. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/pyproject.toml.orig +5 -5
  4. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/app.py +12 -11
  5. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/errors.py +87 -32
  6. dirigent_server-0.17.0/src/dirigent_server/messages.py +99 -0
  7. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/pagination.py +7 -8
  8. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/alerts.py +32 -43
  9. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/auth.py +17 -21
  10. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/blocks.py +4 -2
  11. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/connections.py +35 -29
  12. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/examples.py +2 -9
  13. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/pipelines.py +56 -90
  14. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/runs.py +35 -27
  15. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/schemas.py +11 -15
  16. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/trigger_documents.py +4 -2
  17. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/triggers.py +21 -48
  18. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/users.py +17 -39
  19. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/security.py +27 -14
  20. dirigent_server-0.17.0/src/dirigent_server/static/assets/AdminOnly-922QAPCr.js +1 -0
  21. dirigent_server-0.16.6/src/dirigent_server/static/assets/Alerting-BdqGLqYQ.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Alerting-BDQj-wWG.js +1 -1
  22. dirigent_server-0.16.6/src/dirigent_server/static/assets/Blocks-Bvt6Q0LZ.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Blocks-C81sBLSy.js +1 -1
  23. dirigent_server-0.16.6/src/dirigent_server/static/assets/CodeEditor-BhtcpaBC.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/CodeEditor-0KFNLzta.js +104 -104
  24. dirigent_server-0.16.6/src/dirigent_server/static/assets/CodeEditor-B55U_qvj.css → dirigent_server-0.17.0/src/dirigent_server/static/assets/CodeEditor-C4OnjBMt.css +1 -1
  25. dirigent_server-0.17.0/src/dirigent_server/static/assets/Connections-BQnSPRNV.js +1 -0
  26. dirigent_server-0.16.6/src/dirigent_server/static/assets/Dashboard-BwIbBUdR.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Dashboard--ZePo4SA.js +1 -1
  27. dirigent_server-0.16.6/src/dirigent_server/static/assets/ExamplePanel-DGZ4-_fc.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/ExamplePanel-BAKDvPSI.js +1 -1
  28. dirigent_server-0.17.0/src/dirigent_server/static/assets/Examples-DhaoUWWI.js +2 -0
  29. dirigent_server-0.16.6/src/dirigent_server/static/assets/JsonBlock-Bj16Tcfr.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/JsonBlock-B6EXc4Jh.js +1 -1
  30. dirigent_server-0.16.6/src/dirigent_server/static/assets/ListTable-405QAql8.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/ListTable-CKU-ULuI.js +1 -1
  31. dirigent_server-0.16.6/src/dirigent_server/static/assets/Overview-C5ki0B7e.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Overview-CLoGCCLA.js +1 -1
  32. dirigent_server-0.16.6/src/dirigent_server/static/assets/PageState-Cp732SGc.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/PageState-CGVhOaOE.js +1 -1
  33. dirigent_server-0.16.6/src/dirigent_server/static/assets/Panel-wjrXUPsz.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Panel-CqrOYByl.js +1 -1
  34. dirigent_server-0.16.6/src/dirigent_server/static/assets/Picker-JUhv3zxH.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Picker-Cn4WWjGu.js +1 -1
  35. dirigent_server-0.17.0/src/dirigent_server/static/assets/PipelineEditor-CRepUJaS.js +2 -0
  36. dirigent_server-0.16.6/src/dirigent_server/static/assets/PipelineGraph-PTu4uDur.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/PipelineGraph-f0hlgUJa.js +1 -1
  37. dirigent_server-0.17.0/src/dirigent_server/static/assets/PipelinePreview-aGl-aiEy.js +1 -0
  38. dirigent_server-0.16.6/src/dirigent_server/static/assets/PipelineTab-9AT_bfkO.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/PipelineTab-CToQhytj.js +1 -1
  39. dirigent_server-0.16.6/src/dirigent_server/static/assets/Refusal-BoSSDmiI.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Refusal-7YedH2sJ.js +1 -1
  40. dirigent_server-0.16.6/src/dirigent_server/static/assets/RunDetail-V7O-lHA2.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/RunDetail-vu4Db77w.js +3 -3
  41. dirigent_server-0.16.6/src/dirigent_server/static/assets/RunGraph-DbRxnjUA.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/RunGraph-DZkfC7FS.js +1 -1
  42. dirigent_server-0.17.0/src/dirigent_server/static/assets/SchemaForm-BojGlBVc.js +1 -0
  43. dirigent_server-0.17.0/src/dirigent_server/static/assets/Schemas-DhAypIFr.js +10 -0
  44. dirigent_server-0.17.0/src/dirigent_server/static/assets/SettingsDialog-D0vUJwgm.js +1 -0
  45. dirigent_server-0.16.6/src/dirigent_server/static/assets/StarterPicker-BTu5dSK-.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/StarterPicker-ChbAerdH.js +1 -1
  46. dirigent_server-0.16.6/src/dirigent_server/static/assets/TileCard-H8ygm0pg.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/TileCard-Be4l_R_7.js +1 -1
  47. dirigent_server-0.16.6/src/dirigent_server/static/assets/Triggers-JGAorGIn.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Triggers-DdqGUyEx.js +1 -1
  48. dirigent_server-0.16.6/src/dirigent_server/static/assets/Users-Do6EocZc.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Users-DS27s7xh.js +1 -1
  49. dirigent_server-0.16.6/src/dirigent_server/static/assets/WindowedPane-BaSXRhTY.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/WindowedPane-B_L6IZvq.js +1 -1
  50. dirigent_server-0.16.6/src/dirigent_server/static/assets/Workers-6I9mDtOk.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/Workers-CSbjQhTq.js +1 -1
  51. dirigent_server-0.16.6/src/dirigent_server/static/assets/WorkersTable-4XrduwwF.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/WorkersTable-CBdORnjb.js +1 -1
  52. dirigent_server-0.16.6/src/dirigent_server/static/assets/api-BgT8tbGp.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/api-Bm1JZLuC.js +1 -1
  53. dirigent_server-0.16.6/src/dirigent_server/static/assets/auth-Bfdai8PV.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/auth-CHgfcdt9.js +1 -1
  54. dirigent_server-0.16.6/src/dirigent_server/static/assets/badge-jeTEGfya.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/badge-BovCxjd1.js +1 -1
  55. dirigent_server-0.16.6/src/dirigent_server/static/assets/blocks-DQrWVooP.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/blocks-CclbVd8c.js +1 -1
  56. dirigent_server-0.17.0/src/dirigent_server/static/assets/codicon-DCmgc-ay.ttf +0 -0
  57. dirigent_server-0.17.0/src/dirigent_server/static/assets/connections-BuMoMUFi.js +1 -0
  58. dirigent_server-0.16.6/src/dirigent_server/static/assets/dropdown-menu-CAzEbJy4.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/dropdown-menu-B-6CcHKw.js +1 -1
  59. dirigent_server-0.17.0/src/dirigent_server/static/assets/examples-a8AmyCVF.js +1 -0
  60. dirigent_server-0.17.0/src/dirigent_server/static/assets/index-AVwkiv9z.js +53 -0
  61. dirigent_server-0.17.0/src/dirigent_server/static/assets/index-BmsA0Zt5.css +2 -0
  62. dirigent_server-0.16.6/src/dirigent_server/static/assets/input-CfaV2SUH.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/input-1fShkFXn.js +1 -1
  63. dirigent_server-0.16.6/src/dirigent_server/static/assets/jsonMode-RBBZ5Va3.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/jsonMode-tILOm4tQ.js +1 -1
  64. dirigent_server-0.16.6/src/dirigent_server/static/assets/paging-BPj1IA1S.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/paging-CpRIkEky.js +1 -1
  65. dirigent_server-0.16.6/src/dirigent_server/static/assets/pipelines-NdeXXJeR.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/pipelines-DaeuXRxg.js +1 -1
  66. dirigent_server-0.16.6/src/dirigent_server/static/assets/resolveAriaLabelledBy-BwLqNBBB.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/resolveAriaLabelledBy-ClHvyYLK.js +1 -1
  67. dirigent_server-0.17.0/src/dirigent_server/static/assets/schemas-7KY_7aIc.js +1 -0
  68. dirigent_server-0.16.6/src/dirigent_server/static/assets/select-CkWogUuG.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/select-BZLRZoLW.js +1 -1
  69. dirigent_server-0.16.6/src/dirigent_server/static/assets/tooltip-S6Jq0v8d.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/tooltip-D7_KwbOU.js +1 -1
  70. dirigent_server-0.16.6/src/dirigent_server/static/assets/triggers-Cu9QYlwa.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/triggers-B42obFCk.js +1 -1
  71. dirigent_server-0.16.6/src/dirigent_server/static/assets/users-CtRHdoHp.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/users-CTGHiHMA.js +1 -1
  72. dirigent_server-0.16.6/src/dirigent_server/static/assets/workers-CJAzSpj-.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/workers-BH2Za19E.js +1 -1
  73. dirigent_server-0.16.6/src/dirigent_server/static/assets/yaml-OoeOcOvp.js → dirigent_server-0.17.0/src/dirigent_server/static/assets/yaml-DSCysg4a.js +1 -1
  74. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/index.html +11 -11
  75. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/ui.py +4 -3
  76. dirigent_server-0.16.6/src/dirigent_server/static/assets/AdminOnly-B5Il8Luo.js +0 -1
  77. dirigent_server-0.16.6/src/dirigent_server/static/assets/Connections-L2Owhe12.js +0 -1
  78. dirigent_server-0.16.6/src/dirigent_server/static/assets/Examples-B9amGuk5.js +0 -2
  79. dirigent_server-0.16.6/src/dirigent_server/static/assets/PipelineEditor-Dk3LVCaa.js +0 -2
  80. dirigent_server-0.16.6/src/dirigent_server/static/assets/PipelinePreview-CWIBc0N3.js +0 -1
  81. dirigent_server-0.16.6/src/dirigent_server/static/assets/SchemaForm-Dm5VNXHl.js +0 -1
  82. dirigent_server-0.16.6/src/dirigent_server/static/assets/Schemas-wEdpjEsb.js +0 -10
  83. dirigent_server-0.16.6/src/dirigent_server/static/assets/SettingsDialog-Cq_JECkv.js +0 -1
  84. dirigent_server-0.16.6/src/dirigent_server/static/assets/connections-U2CtRDTF.js +0 -1
  85. dirigent_server-0.16.6/src/dirigent_server/static/assets/examples-CIOjx6Md.js +0 -1
  86. dirigent_server-0.16.6/src/dirigent_server/static/assets/index-C-w0IEq0.css +0 -2
  87. dirigent_server-0.16.6/src/dirigent_server/static/assets/index-DCrQROGm.js +0 -53
  88. dirigent_server-0.16.6/src/dirigent_server/static/assets/schema-form-BKTNSwhM.js +0 -1
  89. dirigent_server-0.16.6/src/dirigent_server/static/assets/schemas-BkE5ckCD.js +0 -1
  90. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/LICENSE +0 -0
  91. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/README.md +0 -0
  92. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/THIRD_PARTY_NOTICES.md +0 -0
  93. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/__init__.py +0 -0
  94. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/dependencies.py +0 -0
  95. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/health.py +0 -0
  96. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/logging.py +0 -0
  97. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/py.typed +0 -0
  98. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/__init__.py +0 -0
  99. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/hooks.py +0 -0
  100. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/schema.py +0 -0
  101. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/system.py +0 -0
  102. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/workers.py +0 -0
  103. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/.gitkeep +0 -0
  104. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Breadcrumb-x6p00HyX.js +0 -0
  105. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Description-MmXUnb-9.js +0 -0
  106. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Instant-mr1E5jAK.js +0 -0
  107. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/KindChip-CwU_Xxdy.js +0 -0
  108. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Markdown-roa7jpVu.js +0 -0
  109. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ProgramReference-DF37mMAT.js +0 -0
  110. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Segmented-VHBNaGnm.js +0 -0
  111. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/StepPorts-KFpmX5hF.js +0 -0
  112. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/chevron-down-DAO3SUWv.js +0 -0
  113. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/chunk-OB3PAWPO-DOp4K52_.js +0 -0
  114. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/copy-BeBwg8IR.js +0 -0
  115. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/dist-DD8kpv1W.js +0 -0
  116. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/docs-CCoB3shg.js +0 -0
  117. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/editor.worker-CIvRzd4O.js +0 -0
  118. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/file-braces-Ep8AP5Yq.js +0 -0
  119. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/format-B5YbYOY-.js +0 -0
  120. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  121. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  122. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  123. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  124. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-600-normal-CTOM6hUh.woff2 +0 -0
  125. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-600-normal-fLZuRloM.woff +0 -0
  126. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  127. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  128. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  129. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  130. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-ext-600-normal-9HEixskS.woff +0 -0
  131. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-cyrillic-ext-600-normal-V-xxqcpd.woff2 +0 -0
  132. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  133. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  134. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  135. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  136. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  137. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  138. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  139. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  140. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  141. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  142. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-ext-600-normal-D38SheWl.woff2 +0 -0
  143. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-latin-ext-600-normal-DmB0ttJJ.woff +0 -0
  144. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  145. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  146. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  147. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  148. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-vietnamese-600-normal-D2EvbN8M.woff2 +0 -0
  149. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-mono-vietnamese-600-normal-iLQfcSjf.woff +0 -0
  150. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  151. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  152. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  153. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  154. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  155. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  156. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/identity-BNZxCf_y.js +0 -0
  157. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/json.worker-fczZDBja.js +0 -0
  158. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/maximize-2-BdFIT4cn.js +0 -0
  159. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/nav-i7bJ0lrA.js +0 -0
  160. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/play-CPVSEWx6.js +0 -0
  161. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/preferences-DsqSnjg9.js +0 -0
  162. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/preload-helper-BZ1Pz5am.js +0 -0
  163. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/react-CQOZoUwg.js +0 -0
  164. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/rolldown-runtime-Dd_uD5pT.js +0 -0
  165. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/shell-CC2rA5mh.js +0 -0
  166. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/sql-NEE52Syq.js +0 -0
  167. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/starters-CvbebKUN.js +0 -0
  168. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/status-Ctx8x8Mf.js +0 -0
  169. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/store-C2zgoyrT.js +0 -0
  170. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/textarea-ljIbN_md.js +0 -0
  171. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/twig-De2hgUGE.js +0 -0
  172. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/use-heartbeat-CBT8848s.js +0 -0
  173. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/use-store-CfRquqZF.js +0 -0
  174. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/utils-CT7ZGLfR.js +0 -0
  175. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/with-selector-BXYKoaOE.js +0 -0
  176. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/x-Dltw6Vbs.js +0 -0
  177. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/yaml.worker-B2SaT_wA.js +0 -0
  178. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/favicon-32.png +0 -0
  179. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/logo.png +0 -0
  180. {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/transactions.py +0 -0
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirigent-server
3
- Version: 0.16.6
3
+ Version: 0.17.0
4
4
  Summary: Dirigent FastAPI server: the REST API, its authentication, and the health probes.
5
5
  License-Expression: LicenseRef-Proprietary
6
6
  License-File: LICENSE
7
7
  License-File: THIRD_PARTY_NOTICES.md
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.13
10
- Requires-Dist: dirigent-client==0.16.6
11
- Requires-Dist: dirigent-common==0.16.6
12
- Requires-Dist: dirigent-core==0.16.6
13
- Requires-Dist: dirigent-examples==0.16.6
10
+ Requires-Dist: dirigent-client==0.17.0
11
+ Requires-Dist: dirigent-common==0.17.0
12
+ Requires-Dist: dirigent-core==0.17.0
13
+ Requires-Dist: dirigent-examples==0.17.0
14
14
  Requires-Dist: fastapi>=0.141.1
15
15
  Requires-Python: >=3.13
16
16
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dirigent-server"
3
- version = "0.16.6"
3
+ version = "0.17.0"
4
4
  description = "Dirigent FastAPI server: the REST API, its authentication, and the health probes."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -14,10 +14,10 @@ classifiers = [
14
14
  "Programming Language :: Python :: 3.13",
15
15
  ]
16
16
  dependencies = [
17
- "dirigent-client==0.16.6",
18
- "dirigent-common==0.16.6",
19
- "dirigent-core==0.16.6",
20
- "dirigent-examples==0.16.6",
17
+ "dirigent-client==0.17.0",
18
+ "dirigent-common==0.17.0",
19
+ "dirigent-core==0.17.0",
20
+ "dirigent-examples==0.17.0",
21
21
  "fastapi>=0.141.1",
22
22
  ]
23
23
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dirigent-server"
3
- version = "0.16.6"
3
+ version = "0.17.0"
4
4
  description = "Dirigent FastAPI server: the REST API, its authentication, and the health probes."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -11,10 +11,10 @@ classifiers = [
11
11
  "Programming Language :: Python :: 3.13",
12
12
  ]
13
13
  dependencies = [
14
- "dirigent-client==0.16.6",
15
- "dirigent-common==0.16.6",
16
- "dirigent-core==0.16.6",
17
- "dirigent-examples==0.16.6",
14
+ "dirigent-client==0.17.0",
15
+ "dirigent-common==0.17.0",
16
+ "dirigent-core==0.17.0",
17
+ "dirigent-examples==0.17.0",
18
18
  "fastapi>=0.141.1",
19
19
  ]
20
20
 
@@ -60,21 +60,23 @@ def create_app(
60
60
 
61
61
  @asynccontextmanager
62
62
  async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
63
- """Open the database engine for the process, and dispose of it on shutdown."""
63
+ """Open the database engine for the process, and dispose of it on every exit path."""
64
64
  engine = create_engine(resolved)
65
65
  app.state.engine = engine
66
66
  app.state.session_factory = create_session_factory(engine)
67
67
  app.state.health_checks = build_registry(engine)
68
- await _bootstrap(app)
69
- await _apply_startup_directory(app)
70
- clock = await _start_scheduler(app, embed=embed)
71
- _logger.info(
72
- "server starting",
73
- database="sqlite" if resolved.is_sqlite else "postgresql",
74
- blocks=len(app.state.services.host.block_ids),
75
- scheduler=embed,
76
- )
68
+ app.state.scheduler = None
69
+ clock: asyncio.Task[None] | None = None
77
70
  try:
71
+ await _bootstrap(app)
72
+ await _apply_startup_directory(app)
73
+ clock = await _start_scheduler(app, embed=embed)
74
+ _logger.info(
75
+ "server starting",
76
+ database="sqlite" if resolved.is_sqlite else "postgresql",
77
+ blocks=len(app.state.services.host.block_ids),
78
+ scheduler=embed,
79
+ )
78
80
  yield
79
81
  finally:
80
82
  await _stop_scheduler(app, clock)
@@ -105,7 +107,6 @@ def create_app(
105
107
 
106
108
  async def _start_scheduler(app: FastAPI, *, embed: bool) -> asyncio.Task[None] | None:
107
109
  """Start the embedded scheduler as a task in the API's own event loop."""
108
- app.state.scheduler = None
109
110
  if not embed:
110
111
  return None
111
112
  scheduler = Scheduler(app.state.session_factory, app.state.services)
@@ -1,6 +1,6 @@
1
1
  """One error envelope for every refusal, in the shape RFC 9457 describes."""
2
2
 
3
- from collections.abc import Awaitable, Callable
3
+ from collections.abc import Awaitable, Callable, Mapping, Sequence
4
4
  from http import HTTPStatus
5
5
  from typing import Any, cast
6
6
 
@@ -10,46 +10,61 @@ from fastapi.responses import JSONResponse
10
10
  from starlette.exceptions import HTTPException as StarletteHTTPException
11
11
 
12
12
  from dirigent_client.schemas import Problem
13
+ from dirigent_common import Issue, JsonMap, Message, validation_issues
13
14
  from dirigent_core import __version__
15
+ from dirigent_core.errors import DomainError
14
16
  from dirigent_core.logging import redact_path
15
17
  from dirigent_server.logging import get_logger
18
+ from dirigent_server.messages import HTTP_ERROR, INTERNAL, REQUEST_INVALID
16
19
 
17
20
  VERSION_HEADER = "X-Dirigent-Version"
18
21
 
19
- INTERNAL_DETAIL = "the server failed to handle this request; the server log has the detail"
22
+ INTERNAL_DETAIL = INTERNAL.render()
20
23
 
21
24
  _logger = get_logger("errors")
22
25
 
23
26
 
24
- def render(status: int, detail: str, *, problems: list[str] | None = None, instance: str | None = None) -> Problem:
27
+ class Refusal(DomainError):
28
+ """A refusal a route makes for which no core class already exists."""
29
+
30
+ def __init__(
31
+ self,
32
+ message: Message,
33
+ /,
34
+ *,
35
+ status: int = 400,
36
+ headers: Mapping[str, str] | None = None,
37
+ **params: Any,
38
+ ) -> None:
39
+ """Carry the status this refusal is answered with, and any header it has to set."""
40
+ super().__init__(message, **params)
41
+ self.status = status
42
+ self.headers = dict(headers) if headers else None
43
+
44
+
45
+ def render(
46
+ status: int,
47
+ detail: str,
48
+ *,
49
+ code: str,
50
+ params: JsonMap | None = None,
51
+ problems: Sequence[Issue] = (),
52
+ instance: str | None = None,
53
+ ) -> Problem:
25
54
  """Build the one problem shape, from whichever handler is answering."""
26
55
  try:
27
56
  title = HTTPStatus(status).phrase
28
57
  except ValueError: # pragma: no cover - a non-standard status from a plugin
29
58
  title = "Error"
30
- return Problem(status=status, title=title, detail=detail, problems=problems or [], instance=instance)
31
-
32
-
33
- def _problems_of(detail: Any) -> tuple[str, list[str]]:
34
- """Split whatever was raised into one sentence and, when there is one, a list."""
35
- if isinstance(detail, str):
36
- return detail, []
37
- if isinstance(detail, list):
38
- rendered = [_one(item) for item in cast(list[object], detail)]
39
- return "; ".join(rendered), rendered
40
- return str(detail), []
41
-
42
-
43
- def _one(item: object) -> str:
44
- """Render one entry of a problem list, whether it is a string or a pydantic error."""
45
- if isinstance(item, str):
46
- return item
47
- if isinstance(item, dict):
48
- mapping = cast(dict[str, Any], item)
49
- location = ".".join(str(part) for part in mapping.get("loc", []))
50
- message = str(mapping.get("msg", mapping))
51
- return f"{location}: {message}" if location else message
52
- return str(item)
59
+ return Problem(
60
+ status=status,
61
+ title=title,
62
+ detail=detail,
63
+ code=code,
64
+ params=params or {},
65
+ problems=list(problems),
66
+ instance=instance,
67
+ )
53
68
 
54
69
 
55
70
  def _where(request: Request) -> str:
@@ -74,11 +89,18 @@ async def _stamp_version(request: Request, call_next: Callable[[Request], Awaita
74
89
 
75
90
 
76
91
  async def http_error(request: Request, error: Exception) -> JSONResponse:
77
- """Render an HTTPException as a problem."""
92
+ """Render an HTTPException as a problem: the framework's own 404, 405 and the like."""
78
93
  if not isinstance(error, StarletteHTTPException): # pragma: no cover - registered for this class
79
94
  return await unhandled(request, error)
80
- detail, problems = _problems_of(error.detail)
81
- response = answer(render(error.status_code, detail, problems=problems, instance=_where(request)))
95
+ detail = str(error.detail)
96
+ problem = render(
97
+ error.status_code,
98
+ detail,
99
+ code=HTTP_ERROR.code,
100
+ params={"status": error.status_code, "detail": detail},
101
+ instance=_where(request),
102
+ )
103
+ response = answer(problem)
82
104
  if isinstance(error, HTTPException) and error.headers:
83
105
  response.headers.update(error.headers)
84
106
  return response
@@ -88,8 +110,40 @@ async def validation_error(request: Request, error: Exception) -> JSONResponse:
88
110
  """Render a request-validation failure as a problem with its field list intact."""
89
111
  if not isinstance(error, RequestValidationError): # pragma: no cover - registered for this class
90
112
  return await unhandled(request, error)
91
- detail, problems = _problems_of(error.errors())
92
- return answer(render(422, detail, problems=problems, instance=_where(request)))
113
+ issues = validation_issues(cast("list[dict[str, Any]]", error.errors()))
114
+ detail = "; ".join(str(issue) for issue in issues)
115
+ return answer(
116
+ render(
117
+ 422,
118
+ detail,
119
+ code=REQUEST_INVALID.code,
120
+ params={"detail": detail},
121
+ problems=issues,
122
+ instance=_where(request),
123
+ )
124
+ )
125
+
126
+
127
+ async def domain_error(request: Request, error: Exception) -> JSONResponse:
128
+ """Render a refusal raised anywhere in the domain, at the status its class carries."""
129
+ if not isinstance(error, DomainError): # pragma: no cover - registered for this class
130
+ return await unhandled(request, error)
131
+ # A refusal that carries a list answers with the list: the problems are the refusal, and
132
+ # the sentence before them only repeats what the caller can read there.
133
+ listed = "; ".join(str(issue) for issue in error.problems)
134
+ problem = render(
135
+ error.status,
136
+ listed or str(error),
137
+ code=error.code,
138
+ params=error.params,
139
+ problems=error.problems,
140
+ instance=_where(request),
141
+ )
142
+ response = answer(problem)
143
+ headers = getattr(error, "headers", None)
144
+ if headers:
145
+ response.headers.update(cast("Mapping[str, str]", headers))
146
+ return response
93
147
 
94
148
 
95
149
  async def unhandled(request: Request, error: Exception) -> JSONResponse:
@@ -105,7 +159,7 @@ async def unhandled(request: Request, error: Exception) -> JSONResponse:
105
159
  error=f"{type(error).__name__}: {error}",
106
160
  exc_info=error,
107
161
  )
108
- return answer(render(500, INTERNAL_DETAIL, instance=_where(request)))
162
+ return answer(render(500, INTERNAL_DETAIL, code=INTERNAL.code, instance=_where(request)))
109
163
 
110
164
 
111
165
  def install_error_handlers(app: FastAPI) -> None:
@@ -113,4 +167,5 @@ def install_error_handlers(app: FastAPI) -> None:
113
167
  app.middleware("http")(_stamp_version)
114
168
  app.add_exception_handler(StarletteHTTPException, http_error)
115
169
  app.add_exception_handler(RequestValidationError, validation_error)
170
+ app.add_exception_handler(DomainError, domain_error)
116
171
  app.add_exception_handler(Exception, unhandled)
@@ -0,0 +1,99 @@
1
+ """Every refusal the API makes that no core class already names, catalogued."""
2
+
3
+ from dirigent_common import Catalogue
4
+
5
+ SERVER = Catalogue("server")
6
+
7
+ REQUEST_INVALID = SERVER.define("request_invalid", "{detail}")
8
+
9
+ HTTP_ERROR = SERVER.define("http", "{detail}")
10
+
11
+ INTERNAL = SERVER.define("internal", "the server failed to handle this request; the server log has the detail")
12
+
13
+ UNAUTHENTICATED = SERVER.define("unauthenticated", "authentication required: present a bearer token or log in")
14
+
15
+ FORBIDDEN = SERVER.define("forbidden", "not permitted for your role")
16
+
17
+ CROSS_SITE = SERVER.define(
18
+ "cross_site",
19
+ "this write was initiated by another site, and a session cookie may not be spent across "
20
+ "origins; automation authenticating with a bearer token is unaffected",
21
+ )
22
+
23
+ BAD_CREDENTIALS = SERVER.define("bad_credentials", "invalid username or password")
24
+
25
+ TOO_MANY_LOGINS = SERVER.define(
26
+ "too_many_logins",
27
+ "too many login attempts; this instance accepts {per_minute} a minute",
28
+ )
29
+
30
+ NO_TOKEN = SERVER.define("no_token", "no live token named {name}")
31
+
32
+ NO_USER_TOKEN = SERVER.define("no_user_token", "no live token named {name} for {username}")
33
+
34
+ ACCOUNT_GONE = SERVER.define("account_gone", "the authenticated account is gone")
35
+
36
+ NO_USER = SERVER.define("no_user", "no user named {username}")
37
+
38
+ NO_NOTIFICATION = SERVER.define("no_notification", "no notification {id}")
39
+
40
+ NO_ALERT_RULE = SERVER.define("no_alert_rule", "no alert rule coded {code}")
41
+
42
+ NO_TRIGGER_DOCUMENT = SERVER.define("no_trigger_document", "no triggers document coded {code}")
43
+
44
+ NO_CONNECTION = SERVER.define("no_connection", "no connection coded {code}")
45
+
46
+ CONNECTION_EXISTS = SERVER.define("connection_exists", "a connection coded {code} exists")
47
+
48
+ CONNECTION_REFERENCED = SERVER.define(
49
+ "connection_referenced",
50
+ "connection {code} is still referenced; delete what uses it first",
51
+ )
52
+
53
+ REDACTED_SECRET = SERVER.define(
54
+ "redacted_secret",
55
+ "{fields} came back as {redacted}, which is what a read shows for a secret "
56
+ "that is set, not a secret. Send the real value, or leave the field out to keep what is stored.",
57
+ )
58
+
59
+ UNKNOWN_CONNECTION_KIND = SERVER.define("unknown_connection_kind", "no connection kind {kind} is installed ({known})")
60
+
61
+ CONNECTION_CONFIG_INVALID = SERVER.define("connection_config_invalid", "{detail}")
62
+
63
+ NO_SCHEMA = SERVER.define("no_schema", "no schema coded {code}")
64
+
65
+ SCHEMA_EXISTS = SERVER.define("schema_exists", "a schema coded {code} exists")
66
+
67
+ DOCUMENT_REFUSED = SERVER.define("document_refused", "{detail}")
68
+
69
+ PRUNE_NAMES_NOTHING = SERVER.define(
70
+ "prune_names_nothing",
71
+ "keep names no codes, and pruning against an empty set would deactivate every directory pipeline",
72
+ )
73
+
74
+ NO_BLOCK = SERVER.define("no_block", "no block {block_id} is installed")
75
+
76
+ NO_RUN = SERVER.define("no_run", "no run {run_id}")
77
+
78
+ RUN_DEFINITION_GONE = SERVER.define("run_definition_gone", "the run's definition is gone")
79
+
80
+ BAD_SINCE = SERVER.define("bad_since", "{detail}")
81
+
82
+ NOT_A_DURATION = SERVER.define("not_a_duration", "{since} is not a duration")
83
+
84
+ NO_ARTIFACT = SERVER.define("no_artifact", "no artifact {artifact_id}")
85
+
86
+ ARTIFACT_EMPTY = SERVER.define("artifact_empty", "artifact {artifact_id} holds no content")
87
+
88
+ IDEMPOTENCY_KEY_REQUIRED = SERVER.define(
89
+ "idempotency_key_required",
90
+ "an Idempotency-Key header is required, so a retried request creates one attempt",
91
+ )
92
+
93
+ NO_ATTEMPT = SERVER.define("no_attempt", "no attempt {attempt_id}")
94
+
95
+ TOO_MANY_TAILS = SERVER.define("too_many_tails", "you already have {maximum} streams open on this server")
96
+
97
+ NO_ICON = SERVER.define("no_icon", "this bundle carries no icon")
98
+
99
+ BAD_CURSOR = SERVER.define("bad_cursor", "{name}={after} is not a cursor this listing gave out")
@@ -11,7 +11,10 @@ from collections.abc import Callable
11
11
  from typing import Annotated
12
12
  from uuid import UUID
13
13
 
14
- from fastapi import HTTPException, Query, status
14
+ from fastapi import Query, status
15
+
16
+ from dirigent_server.errors import Refusal
17
+ from dirigent_server.messages import BAD_CURSOR
15
18
 
16
19
  DEFAULT_PAGE = 50
17
20
 
@@ -37,9 +40,8 @@ def uuid_cursor(after: str | None) -> UUID | None:
37
40
  try:
38
41
  return UUID(after)
39
42
  except ValueError as error:
40
- raise HTTPException(
41
- status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
42
- detail=f"after={after!r} is not a cursor this listing gave out",
43
+ raise Refusal(
44
+ BAD_CURSOR, status=status.HTTP_422_UNPROCESSABLE_CONTENT, name="after", after=repr(after)
43
45
  ) from error
44
46
 
45
47
 
@@ -50,7 +52,4 @@ def int_cursor(after: str | None, *, name: str = "after") -> int | None:
50
52
  try:
51
53
  return int(after)
52
54
  except ValueError as error:
53
- raise HTTPException(
54
- status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
55
- detail=f"{name}={after!r} is not a cursor this listing gave out",
56
- ) from error
55
+ raise Refusal(BAD_CURSOR, status=status.HTTP_422_UNPROCESSABLE_CONTENT, name=name, after=repr(after)) from error
@@ -4,7 +4,7 @@ from datetime import timedelta
4
4
  from typing import Annotated
5
5
  from uuid import UUID
6
6
 
7
- from fastapi import APIRouter, HTTPException, Query, Response, status
7
+ from fastapi import APIRouter, Query, Response, status
8
8
 
9
9
  from dirigent_client.enums import NotificationStatus
10
10
  from dirigent_client.schemas import (
@@ -18,7 +18,6 @@ from dirigent_client.schemas import (
18
18
  )
19
19
  from dirigent_common.durations import format_duration
20
20
  from dirigent_core.alerting import (
21
- AlertError,
22
21
  AlertRuleRequest,
23
22
  create_rule,
24
23
  delete_rule,
@@ -32,6 +31,8 @@ from dirigent_core.alerting import (
32
31
  )
33
32
  from dirigent_core.models import AlertRule, Connection, Notification, Pipeline, Run
34
33
  from dirigent_server.dependencies import ServicesDep, SessionDep
34
+ from dirigent_server.errors import Refusal
35
+ from dirigent_server.messages import NO_ALERT_RULE, NO_NOTIFICATION
35
36
  from dirigent_server.pagination import DEFAULT_PAGE, AfterParam, LimitParam, clip, uuid_cursor
36
37
  from dirigent_server.security import OperatorDep, PrincipalDep
37
38
  from dirigent_server.transactions import Transactional
@@ -94,26 +95,23 @@ async def add_rule(
94
95
  payload: AlertRuleIn, session: SessionDep, services: ServicesDep, principal: OperatorDep
95
96
  ) -> AlertRuleOut:
96
97
  """Declare an alert rule, refusing a notifier or a pipeline this instance does not have."""
97
- try:
98
- rule = await create_rule(
99
- session,
100
- services,
101
- AlertRuleRequest(
102
- code=payload.code,
103
- name=payload.name,
104
- description=payload.description,
105
- event=payload.event,
106
- notifier=payload.notifier,
107
- scope=payload.scope,
108
- pipeline=payload.pipeline,
109
- connection=payload.connection,
110
- template=payload.template,
111
- body=payload.body,
112
- throttle=payload.throttle,
113
- ),
114
- )
115
- except AlertError as error:
116
- raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(error)) from error
98
+ rule = await create_rule(
99
+ session,
100
+ services,
101
+ AlertRuleRequest(
102
+ code=payload.code,
103
+ name=payload.name,
104
+ description=payload.description,
105
+ event=payload.event,
106
+ notifier=payload.notifier,
107
+ scope=payload.scope,
108
+ pipeline=payload.pipeline,
109
+ connection=payload.connection,
110
+ template=payload.template,
111
+ body=payload.body,
112
+ throttle=payload.throttle,
113
+ ),
114
+ )
117
115
  return render(rule, payload.pipeline, payload.connection)
118
116
 
119
117
 
@@ -131,10 +129,7 @@ async def change_rule(code: str, payload: AlertRuleUpdate, session: SessionDep,
131
129
  the caller left out is left as it was.
132
130
  """
133
131
  rule = await _rule_or_404(session, code)
134
- try:
135
- await update_rule(session, rule, payload.model_dump(exclude_unset=True))
136
- except AlertError as error:
137
- raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(error)) from error
132
+ await update_rule(session, rule, payload.model_dump(exclude_unset=True))
138
133
  return render(rule, await _pipeline_code(session, rule), await _connection_code(session, rule.connection_id))
139
134
 
140
135
 
@@ -161,17 +156,14 @@ async def test_notifier(
161
156
  payload: TestRequest, session: SessionDep, services: ServicesDep, principal: OperatorDep
162
157
  ) -> TestQueued:
163
158
  """Queue one message through a channel, on the same path a real alert takes."""
164
- try:
165
- notification = await queue_test_message(
166
- session,
167
- services,
168
- notifier=payload.notifier,
169
- connection=payload.connection,
170
- subject=payload.subject,
171
- body=payload.body,
172
- )
173
- except AlertError as error:
174
- raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(error)) from error
159
+ notification = await queue_test_message(
160
+ session,
161
+ services,
162
+ notifier=payload.notifier,
163
+ connection=payload.connection,
164
+ subject=payload.subject,
165
+ body=payload.body,
166
+ )
175
167
  return TestQueued(notification_id=notification.id, notifier=notification.notifier)
176
168
 
177
169
 
@@ -231,10 +223,7 @@ async def retry(
231
223
  ) -> NotificationOut:
232
224
  """Make one notification due now, so a worker takes it on its next pass."""
233
225
  row = await _notification_or_404(session, notification_id)
234
- try:
235
- await retry_notification(session, row)
236
- except AlertError as error:
237
- raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(error)) from error
226
+ await retry_notification(session, row)
238
227
  return await _notification(session, services, row)
239
228
 
240
229
 
@@ -267,7 +256,7 @@ async def _notification_or_404(session: SessionDep, notification_id: UUID) -> No
267
256
  """Find one notification, or refuse with the id that named nothing."""
268
257
  row = await find_notification(session, notification_id)
269
258
  if row is None:
270
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"no notification {notification_id}")
259
+ raise Refusal(NO_NOTIFICATION, status=status.HTTP_404_NOT_FOUND, id=notification_id)
271
260
  return row
272
261
 
273
262
 
@@ -275,7 +264,7 @@ async def _rule_or_404(session: SessionDep, code: str) -> AlertRule:
275
264
  """Find one alert rule, or refuse with the code that named nothing."""
276
265
  rule = await find_rule(session, code)
277
266
  if rule is None:
278
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"no alert rule coded {code!r}")
267
+ raise Refusal(NO_ALERT_RULE, status=status.HTTP_404_NOT_FOUND, code=repr(code))
279
268
  return rule
280
269
 
281
270
 
@@ -1,6 +1,6 @@
1
1
  """Logging in, logging out, and asking who you are."""
2
2
 
3
- from fastapi import APIRouter, HTTPException, Request, Response, status
3
+ from fastapi import APIRouter, Request, Response, status
4
4
 
5
5
  from dirigent_client.enums import TokenKind
6
6
  from dirigent_client.schemas import (
@@ -15,8 +15,6 @@ from dirigent_client.schemas import (
15
15
  from dirigent_core.auth import (
16
16
  SESSION_LIFETIME,
17
17
  Principal,
18
- WeakPassword,
19
- WrongPassword,
20
18
  authenticate,
21
19
  change_password,
22
20
  issue_token,
@@ -28,7 +26,9 @@ from dirigent_core.auth import find_user as find_user_row
28
26
  from dirigent_core.models import User
29
27
  from dirigent_core.ratelimit import TokenBucket
30
28
  from dirigent_server.dependencies import SessionDep, SettingsDep
29
+ from dirigent_server.errors import Refusal
31
30
  from dirigent_server.logging import get_logger
31
+ from dirigent_server.messages import ACCOUNT_GONE, BAD_CREDENTIALS, NO_TOKEN, TOO_MANY_LOGINS
32
32
  from dirigent_server.pagination import DEFAULT_PAGE, AfterParam, LimitParam, clip, uuid_cursor
33
33
  from dirigent_server.security import (
34
34
  AdminDep,
@@ -68,7 +68,7 @@ async def login(
68
68
  _limit_login(request, payload.username, per_minute=settings.login_rate_per_minute)
69
69
  user = await authenticate(session, payload.username, payload.password.get_secret_value())
70
70
  if user is None:
71
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid username or password")
71
+ raise Refusal(BAD_CREDENTIALS, status=status.HTTP_401_UNAUTHORIZED)
72
72
  issued = await issue_token(session, user, name="session", kind=TokenKind.SESSION, lifetime=SESSION_LIFETIME)
73
73
  set_session_cookie(
74
74
  response,
@@ -91,10 +91,11 @@ def _limit_login(request: Request, username: str, *, per_minute: int) -> None:
91
91
  for scope, key in (("address", f"address:{address}"), ("account", f"user:{username}")):
92
92
  if not LOGIN_BUCKETS.allow(key, per_minute=per_minute):
93
93
  _logger.warning("login rate limited", scope=scope, address=address, limit=per_minute)
94
- raise HTTPException(
95
- status_code=status.HTTP_429_TOO_MANY_REQUESTS,
96
- detail=f"too many login attempts; this instance accepts {per_minute} a minute",
94
+ raise Refusal(
95
+ TOO_MANY_LOGINS,
96
+ status=status.HTTP_429_TOO_MANY_REQUESTS,
97
97
  headers={"Retry-After": "60"},
98
+ per_minute=per_minute,
98
99
  )
99
100
 
100
101
 
@@ -131,18 +132,13 @@ async def change_own_password(
131
132
  out of the page they made it from. An API token is not a session and is untouched.
132
133
  """
133
134
  user = await _require_user(session, principal.username)
134
- try:
135
- await change_password(
136
- session,
137
- user,
138
- payload.current_password.get_secret_value(),
139
- payload.new_password.get_secret_value(),
140
- keep=principal.token_id,
141
- )
142
- except WrongPassword as error:
143
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(error)) from error
144
- except WeakPassword as error:
145
- raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(error)) from error
135
+ await change_password(
136
+ session,
137
+ user,
138
+ payload.current_password.get_secret_value(),
139
+ payload.new_password.get_secret_value(),
140
+ keep=principal.token_id,
141
+ )
146
142
  return Response(status_code=status.HTTP_204_NO_CONTENT)
147
143
 
148
144
 
@@ -206,7 +202,7 @@ async def create_api_token(payload: TokenRequest, session: SessionDep, principal
206
202
  async def revoke_api_token(name: str, session: SessionDep, principal: AdminDep) -> Response:
207
203
  """Revoke the caller's live tokens of the given name."""
208
204
  if not await revoke_token(session, user_id=principal.user_id, name=name):
209
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"no live token named {name!r}")
205
+ raise Refusal(NO_TOKEN, status=status.HTTP_404_NOT_FOUND, name=repr(name))
210
206
  return Response(status_code=status.HTTP_204_NO_CONTENT)
211
207
 
212
208
 
@@ -214,5 +210,5 @@ async def _require_user(session: SessionDep, username: str) -> User:
214
210
  """Read the account a principal names."""
215
211
  user = await find_user_row(session, username)
216
212
  if user is None: # pragma: no cover - a resolved principal always has its row
217
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="the authenticated account is gone")
213
+ raise Refusal(ACCOUNT_GONE, status=status.HTTP_401_UNAUTHORIZED)
218
214
  return user
@@ -2,10 +2,12 @@
2
2
 
3
3
  from typing import Annotated
4
4
 
5
- from fastapi import APIRouter, HTTPException, Query, status
5
+ from fastapi import APIRouter, Query, status
6
6
 
7
7
  from dirigent_client.schemas import BlockEntry, BlockKind, Catalog
8
8
  from dirigent_server.dependencies import ServicesDep
9
+ from dirigent_server.errors import Refusal
10
+ from dirigent_server.messages import NO_BLOCK
9
11
  from dirigent_server.security import PrincipalDep
10
12
  from dirigent_server.transactions import Transactional
11
13
 
@@ -35,5 +37,5 @@ async def get_block(block_id: str, services: ServicesDep, principal: PrincipalDe
35
37
  """Serve one catalog entry."""
36
38
  entry = services.host.catalog().block(block_id)
37
39
  if entry is None:
38
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"no block {block_id!r} is installed")
40
+ raise Refusal(NO_BLOCK, status=status.HTTP_404_NOT_FOUND, block_id=repr(block_id))
39
41
  return entry