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.
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/PKG-INFO +5 -5
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/pyproject.toml +5 -5
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/pyproject.toml.orig +5 -5
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/app.py +12 -11
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/errors.py +87 -32
- dirigent_server-0.17.0/src/dirigent_server/messages.py +99 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/pagination.py +7 -8
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/alerts.py +32 -43
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/auth.py +17 -21
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/blocks.py +4 -2
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/connections.py +35 -29
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/examples.py +2 -9
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/pipelines.py +56 -90
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/runs.py +35 -27
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/schemas.py +11 -15
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/trigger_documents.py +4 -2
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/triggers.py +21 -48
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/users.py +17 -39
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/security.py +27 -14
- dirigent_server-0.17.0/src/dirigent_server/static/assets/AdminOnly-922QAPCr.js +1 -0
- 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
- 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
- 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
- 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
- dirigent_server-0.17.0/src/dirigent_server/static/assets/Connections-BQnSPRNV.js +1 -0
- 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
- 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
- dirigent_server-0.17.0/src/dirigent_server/static/assets/Examples-DhaoUWWI.js +2 -0
- 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
- 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
- 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
- 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
- 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
- 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
- dirigent_server-0.17.0/src/dirigent_server/static/assets/PipelineEditor-CRepUJaS.js +2 -0
- 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
- dirigent_server-0.17.0/src/dirigent_server/static/assets/PipelinePreview-aGl-aiEy.js +1 -0
- 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
- 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
- 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
- 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
- dirigent_server-0.17.0/src/dirigent_server/static/assets/SchemaForm-BojGlBVc.js +1 -0
- dirigent_server-0.17.0/src/dirigent_server/static/assets/Schemas-DhAypIFr.js +10 -0
- dirigent_server-0.17.0/src/dirigent_server/static/assets/SettingsDialog-D0vUJwgm.js +1 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- dirigent_server-0.17.0/src/dirigent_server/static/assets/codicon-DCmgc-ay.ttf +0 -0
- dirigent_server-0.17.0/src/dirigent_server/static/assets/connections-BuMoMUFi.js +1 -0
- 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
- dirigent_server-0.17.0/src/dirigent_server/static/assets/examples-a8AmyCVF.js +1 -0
- dirigent_server-0.17.0/src/dirigent_server/static/assets/index-AVwkiv9z.js +53 -0
- dirigent_server-0.17.0/src/dirigent_server/static/assets/index-BmsA0Zt5.css +2 -0
- 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
- 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
- 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
- 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
- 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
- dirigent_server-0.17.0/src/dirigent_server/static/assets/schemas-7KY_7aIc.js +1 -0
- 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
- 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
- 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
- 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
- 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
- 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
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/index.html +11 -11
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/ui.py +4 -3
- dirigent_server-0.16.6/src/dirigent_server/static/assets/AdminOnly-B5Il8Luo.js +0 -1
- dirigent_server-0.16.6/src/dirigent_server/static/assets/Connections-L2Owhe12.js +0 -1
- dirigent_server-0.16.6/src/dirigent_server/static/assets/Examples-B9amGuk5.js +0 -2
- dirigent_server-0.16.6/src/dirigent_server/static/assets/PipelineEditor-Dk3LVCaa.js +0 -2
- dirigent_server-0.16.6/src/dirigent_server/static/assets/PipelinePreview-CWIBc0N3.js +0 -1
- dirigent_server-0.16.6/src/dirigent_server/static/assets/SchemaForm-Dm5VNXHl.js +0 -1
- dirigent_server-0.16.6/src/dirigent_server/static/assets/Schemas-wEdpjEsb.js +0 -10
- dirigent_server-0.16.6/src/dirigent_server/static/assets/SettingsDialog-Cq_JECkv.js +0 -1
- dirigent_server-0.16.6/src/dirigent_server/static/assets/connections-U2CtRDTF.js +0 -1
- dirigent_server-0.16.6/src/dirigent_server/static/assets/examples-CIOjx6Md.js +0 -1
- dirigent_server-0.16.6/src/dirigent_server/static/assets/index-C-w0IEq0.css +0 -2
- dirigent_server-0.16.6/src/dirigent_server/static/assets/index-DCrQROGm.js +0 -53
- dirigent_server-0.16.6/src/dirigent_server/static/assets/schema-form-BKTNSwhM.js +0 -1
- dirigent_server-0.16.6/src/dirigent_server/static/assets/schemas-BkE5ckCD.js +0 -1
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/LICENSE +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/README.md +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/THIRD_PARTY_NOTICES.md +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/__init__.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/dependencies.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/health.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/logging.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/py.typed +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/__init__.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/hooks.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/schema.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/system.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/routes/workers.py +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/.gitkeep +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Breadcrumb-x6p00HyX.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Description-MmXUnb-9.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Instant-mr1E5jAK.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/KindChip-CwU_Xxdy.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Markdown-roa7jpVu.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/ProgramReference-DF37mMAT.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/Segmented-VHBNaGnm.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/StepPorts-KFpmX5hF.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/chevron-down-DAO3SUWv.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/chunk-OB3PAWPO-DOp4K52_.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/copy-BeBwg8IR.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/dist-DD8kpv1W.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/docs-CCoB3shg.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/editor.worker-CIvRzd4O.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/file-braces-Ep8AP5Yq.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/format-B5YbYOY-.js +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/identity-BNZxCf_y.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/json.worker-fczZDBja.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/maximize-2-BdFIT4cn.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/nav-i7bJ0lrA.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/play-CPVSEWx6.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/preferences-DsqSnjg9.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/preload-helper-BZ1Pz5am.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/react-CQOZoUwg.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/rolldown-runtime-Dd_uD5pT.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/shell-CC2rA5mh.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/sql-NEE52Syq.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/starters-CvbebKUN.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/status-Ctx8x8Mf.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/store-C2zgoyrT.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/textarea-ljIbN_md.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/twig-De2hgUGE.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/use-heartbeat-CBT8848s.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/use-store-CfRquqZF.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/utils-CT7ZGLfR.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/with-selector-BXYKoaOE.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/x-Dltw6Vbs.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/assets/yaml.worker-B2SaT_wA.js +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/favicon-32.png +0 -0
- {dirigent_server-0.16.6 → dirigent_server-0.17.0}/src/dirigent_server/static/logo.png +0 -0
- {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.
|
|
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.
|
|
11
|
-
Requires-Dist: dirigent-common==0.
|
|
12
|
-
Requires-Dist: dirigent-core==0.
|
|
13
|
-
Requires-Dist: dirigent-examples==0.
|
|
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.
|
|
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.
|
|
18
|
-
"dirigent-common==0.
|
|
19
|
-
"dirigent-core==0.
|
|
20
|
-
"dirigent-examples==0.
|
|
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.
|
|
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.
|
|
15
|
-
"dirigent-common==0.
|
|
16
|
-
"dirigent-core==0.
|
|
17
|
-
"dirigent-examples==0.
|
|
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
|
|
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
|
-
|
|
69
|
-
|
|
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 =
|
|
22
|
+
INTERNAL_DETAIL = INTERNAL.render()
|
|
20
23
|
|
|
21
24
|
_logger = get_logger("errors")
|
|
22
25
|
|
|
23
26
|
|
|
24
|
-
|
|
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(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
81
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
|
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
|
|
41
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
40
|
+
raise Refusal(NO_BLOCK, status=status.HTTP_404_NOT_FOUND, block_id=repr(block_id))
|
|
39
41
|
return entry
|