From 505dd2e73d415bb3ee67884bde4dda2f056f5e2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 May 2024 01:55:04 +0000 Subject: [PATCH 01/49] Bump next from 13.5.6 to 14.1.1 in /playground Bumps [next](https://github.com/vercel/next.js) from 13.5.6 to 14.1.1. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v13.5.6...v14.1.1) --- updated-dependencies: - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- playground/package-lock.json | 119 ++++++++++++--------------- playground/package.json | 2 +- playground/yarn.lock | 151 ++++++++++++++++------------------- 3 files changed, 121 insertions(+), 151 deletions(-) diff --git a/playground/package-lock.json b/playground/package-lock.json index e8636094e..0a2b1a34b 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -21,7 +21,7 @@ "autoprefixer": "10.4.16", "jszip": "3.10.1", "monaco-editor": "0.44.0", - "next": "13.5.6", + "next": "14.1.1", "postcss": "8.4.31", "react": "18.2.0", "react-complex-tree": "2.2.2", @@ -899,9 +899,9 @@ } }, "node_modules/@next/env": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz", - "integrity": "sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==" + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.1.tgz", + "integrity": "sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==" }, "node_modules/@next/eslint-plugin-next": { "version": "13.5.6", @@ -933,9 +933,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz", - "integrity": "sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz", + "integrity": "sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==", "cpu": [ "arm64" ], @@ -948,9 +948,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", - "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz", + "integrity": "sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==", "cpu": [ "x64" ], @@ -963,9 +963,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", - "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz", + "integrity": "sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==", "cpu": [ "arm64" ], @@ -978,9 +978,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", - "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz", + "integrity": "sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==", "cpu": [ "arm64" ], @@ -993,9 +993,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", - "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz", + "integrity": "sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==", "cpu": [ "x64" ], @@ -1008,9 +1008,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", - "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz", + "integrity": "sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==", "cpu": [ "x64" ], @@ -1023,9 +1023,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", - "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz", + "integrity": "sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==", "cpu": [ "arm64" ], @@ -1038,9 +1038,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", - "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz", + "integrity": "sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==", "cpu": [ "ia32" ], @@ -1053,9 +1053,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", - "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz", + "integrity": "sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==", "cpu": [ "x64" ], @@ -2378,9 +2378,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001571", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz", - "integrity": "sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ==", + "version": "1.0.30001617", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", + "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", "funding": [ { "type": "opencollective", @@ -3894,11 +3894,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, "node_modules/globals": { "version": "13.23.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", @@ -5825,34 +5820,34 @@ "dev": true }, "node_modules/next": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/next/-/next-13.5.6.tgz", - "integrity": "sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.1.tgz", + "integrity": "sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==", "dependencies": { - "@next/env": "13.5.6", + "@next/env": "14.1.1", "@swc/helpers": "0.5.2", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0" + "styled-jsx": "5.1.1" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=16.14.0" + "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.5.6", - "@next/swc-darwin-x64": "13.5.6", - "@next/swc-linux-arm64-gnu": "13.5.6", - "@next/swc-linux-arm64-musl": "13.5.6", - "@next/swc-linux-x64-gnu": "13.5.6", - "@next/swc-linux-x64-musl": "13.5.6", - "@next/swc-win32-arm64-msvc": "13.5.6", - "@next/swc-win32-ia32-msvc": "13.5.6", - "@next/swc-win32-x64-msvc": "13.5.6" + "@next/swc-darwin-arm64": "14.1.1", + "@next/swc-darwin-x64": "14.1.1", + "@next/swc-linux-arm64-gnu": "14.1.1", + "@next/swc-linux-arm64-musl": "14.1.1", + "@next/swc-linux-x64-gnu": "14.1.1", + "@next/swc-linux-x64-musl": "14.1.1", + "@next/swc-win32-arm64-msvc": "14.1.1", + "@next/swc-win32-ia32-msvc": "14.1.1", + "@next/swc-win32-x64-msvc": "14.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -8219,18 +8214,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/playground/package.json b/playground/package.json index eae3bd9bb..d0d82cb5c 100644 --- a/playground/package.json +++ b/playground/package.json @@ -26,7 +26,7 @@ "allotment": "1.19.3", "@monaco-editor/react": "4.6.0", "monaco-editor": "0.44.0", - "next": "13.5.6", + "next": "14.1.1", "react": "18.2.0", "react-complex-tree": "2.2.2", "react-complex-tree-blueprintjs-renderers": "2.2.2", diff --git a/playground/yarn.lock b/playground/yarn.lock index 3a4adee7e..25fc7802e 100644 --- a/playground/yarn.lock +++ b/playground/yarn.lock @@ -335,10 +335,10 @@ dependencies: "@monaco-editor/loader" "^1.4.0" -"@next/env@13.5.6": - version "13.5.6" - resolved "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz" - integrity sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw== +"@next/env@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.1.tgz#80150a8440eb0022a73ba353c6088d419b908bac" + integrity sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA== "@next/eslint-plugin-next@13.5.6": version "13.5.6" @@ -347,50 +347,50 @@ dependencies: glob "7.1.7" -"@next/swc-darwin-arm64@13.5.6": - version "13.5.6" - resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz" - integrity sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA== - -"@next/swc-darwin-x64@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz#9c72ee31cc356cb65ce6860b658d807ff39f1578" - integrity sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA== - -"@next/swc-linux-arm64-gnu@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz#59f5f66155e85380ffa26ee3d95b687a770cfeab" - integrity sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg== - -"@next/swc-linux-arm64-musl@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz#f012518228017052736a87d69bae73e587c76ce2" - integrity sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q== - -"@next/swc-linux-x64-gnu@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz#339b867a7e9e7ee727a700b496b269033d820df4" - integrity sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw== - -"@next/swc-linux-x64-musl@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz#ae0ae84d058df758675830bcf70ca1846f1028f2" - integrity sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ== - -"@next/swc-win32-arm64-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz#a5cc0c16920485a929a17495064671374fdbc661" - integrity sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg== - -"@next/swc-win32-ia32-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz#6a2409b84a2cbf34bf92fe714896455efb4191e4" - integrity sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg== - -"@next/swc-win32-x64-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz#4a3e2a206251abc729339ba85f60bc0433c2865d" - integrity sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ== +"@next/swc-darwin-arm64@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz#b74ba7c14af7d05fa2848bdeb8ee87716c939b64" + integrity sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ== + +"@next/swc-darwin-x64@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz#82c3e67775e40094c66e76845d1a36cc29c9e78b" + integrity sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw== + +"@next/swc-linux-arm64-gnu@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz#4f4134457b90adc5c3d167d07dfb713c632c0caa" + integrity sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg== + +"@next/swc-linux-arm64-musl@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz#594bedafaeba4a56db23a48ffed2cef7cd09c31a" + integrity sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ== + +"@next/swc-linux-x64-gnu@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz#cb4e75f1ff2b9bcadf2a50684605928ddfc58528" + integrity sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ== + +"@next/swc-linux-x64-musl@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz#15f26800df941b94d06327f674819ab64b272e25" + integrity sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og== + +"@next/swc-win32-arm64-msvc@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz#060c134fa7fa843666e3e8574972b2b723773dd9" + integrity sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A== + +"@next/swc-win32-ia32-msvc@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz#5c06889352b1f77e3807834a0d0afd7e2d2d1da2" + integrity sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw== + +"@next/swc-win32-x64-msvc@14.1.1": + version "14.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz#d38c63a8f9b7f36c1470872797d3735b4a9c5c52" + integrity sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -1181,10 +1181,10 @@ camelize@^1.0.0: resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz" integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== -caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565: - version "1.0.30001571" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz" - integrity sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ== +caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565, caniuse-lite@^1.0.30001579: + version "1.0.30001617" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz#809bc25f3f5027ceb33142a7d6c40759d7a901eb" + integrity sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA== capital-case@^1.0.4: version "1.0.4" @@ -2119,11 +2119,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - glob@7.1.6: version "7.1.6" resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" @@ -2209,7 +2204,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3307,28 +3302,28 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -next@13.5.6: - version "13.5.6" - resolved "https://registry.npmjs.org/next/-/next-13.5.6.tgz" - integrity sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw== +next@14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/next/-/next-14.1.1.tgz#92bd603996c050422a738e90362dff758459a171" + integrity sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww== dependencies: - "@next/env" "13.5.6" + "@next/env" "14.1.1" "@swc/helpers" "0.5.2" busboy "1.6.0" - caniuse-lite "^1.0.30001406" + caniuse-lite "^1.0.30001579" + graceful-fs "^4.2.11" postcss "8.4.31" styled-jsx "5.1.1" - watchpack "2.4.0" optionalDependencies: - "@next/swc-darwin-arm64" "13.5.6" - "@next/swc-darwin-x64" "13.5.6" - "@next/swc-linux-arm64-gnu" "13.5.6" - "@next/swc-linux-arm64-musl" "13.5.6" - "@next/swc-linux-x64-gnu" "13.5.6" - "@next/swc-linux-x64-musl" "13.5.6" - "@next/swc-win32-arm64-msvc" "13.5.6" - "@next/swc-win32-ia32-msvc" "13.5.6" - "@next/swc-win32-x64-msvc" "13.5.6" + "@next/swc-darwin-arm64" "14.1.1" + "@next/swc-darwin-x64" "14.1.1" + "@next/swc-linux-arm64-gnu" "14.1.1" + "@next/swc-linux-arm64-musl" "14.1.1" + "@next/swc-linux-x64-gnu" "14.1.1" + "@next/swc-linux-x64-musl" "14.1.1" + "@next/swc-win32-arm64-msvc" "14.1.1" + "@next/swc-win32-ia32-msvc" "14.1.1" + "@next/swc-win32-x64-msvc" "14.1.1" no-case@^3.0.4: version "3.0.4" @@ -4789,14 +4784,6 @@ warning@^4.0.2, warning@^4.0.3: dependencies: loose-envify "^1.0.0" -watchpack@2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" From 7769e87fc2afc9e251e7fa9dfcfb838397149025 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 12 May 2024 17:34:12 +0200 Subject: [PATCH 02/49] use unique keys across artemis instances --- .../assessment_module_manager/__main__.py | 2 +- athena/athena/authenticate.py | 17 +++++++++----- athena/athena/endpoints.py | 2 +- athena/athena/env.py | 13 +++++++++-- athena/athena/helpers/list_deployments.py | 22 +++++++++++++++++++ athena/athena/schemas/deployment.py | 7 ++++++ 6 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 athena/athena/helpers/list_deployments.py create mode 100644 athena/athena/schemas/deployment.py diff --git a/assessment_module_manager/assessment_module_manager/__main__.py b/assessment_module_manager/assessment_module_manager/__main__.py index aef21affc..9c4b8d5fa 100644 --- a/assessment_module_manager/assessment_module_manager/__main__.py +++ b/assessment_module_manager/assessment_module_manager/__main__.py @@ -22,7 +22,7 @@ def main(): uvicorn.run("assessment_module_manager.__main__:app", host="0.0.0.0", port=5000) else: logger.warning("Running in DEVELOPMENT mode") - uvicorn.run("assessment_module_manager.__main__:app", host="0.0.0.0", port=5000, reload=True) + uvicorn.run("assessment_module_manager.__main__:app", host="127.0.0.1", port=5000, reload=True) # Add things to __all__ just to mark them as important to import diff --git a/athena/athena/authenticate.py b/athena/athena/authenticate.py index 06ff35722..a37d26469 100644 --- a/athena/athena/authenticate.py +++ b/athena/athena/authenticate.py @@ -8,11 +8,12 @@ from athena import env from athena.logger import logger -api_key_header = APIKeyHeader(name='Authorization', auto_error=False) +api_key_auth_header = APIKeyHeader(name='Authorization', auto_error=False) +api_key_artemis_id_header = APIKeyHeader(name='...', auto_error=False) -def verify_secret(secret: str): - if secret != env.SECRET: +def verify_secret(artemis_id: str, secret: str): + if secret != env.DEPLOYMENT_SECRETS[artemis_id]: if env.PRODUCTION: raise HTTPException(status_code=401, detail="Invalid API secret.") logger.warning("DEBUG MODE: Ignoring invalid API secret.") @@ -29,8 +30,9 @@ def endpoint(): """ @wraps(func) - async def wrapper(*args, secret: str = Depends(api_key_header), **kwargs): - verify_secret(secret) + async def wrapper(*args, secret: str = Depends(api_key_auth_header), + artemis_id: str = Depends(api_key_artemis_id_header), **kwargs): + verify_secret(artemis_id, secret) if inspect.iscoroutinefunction(func): return await func(*args, **kwargs) return func(*args, **kwargs) @@ -38,7 +40,10 @@ async def wrapper(*args, secret: str = Depends(api_key_header), **kwargs): # Update the function signature with the 'secret' parameter, but otherwise keep the annotations intact sig = inspect.signature(func) params = list(sig.parameters.values()) - params.append(inspect.Parameter('secret', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_header))) + params.append( + inspect.Parameter('secret', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_auth_header))) + params.append(inspect.Parameter('artemis_id', inspect.Parameter.POSITIONAL_OR_KEYWORD, + default=Depends(api_key_artemis_id_header))) new_sig = sig.replace(parameters=params) wrapper.__signature__ = new_sig # type: ignore # https://github.com/python/mypy/issues/12472 diff --git a/athena/athena/endpoints.py b/athena/athena/endpoints.py index bc68b2c3e..a610b15a1 100644 --- a/athena/athena/endpoints.py +++ b/athena/athena/endpoints.py @@ -311,7 +311,7 @@ def feedback_provider(func: Union[ async def wrapper( exercise: exercise_type, submission: submission_type, - is_graded: is_graded_type = Body(False), + is_graded: is_graded_type = Body(True), module_config: module_config_type = Depends(get_dynamic_module_config_factory(module_config_type))): # Retrieve existing metadata for the exercise, submission and feedback diff --git a/athena/athena/env.py b/athena/athena/env.py index 1289de35b..1142270a9 100644 --- a/athena/athena/env.py +++ b/athena/athena/env.py @@ -1,6 +1,15 @@ """Common place for environment variables with sensible defaults for local development.""" import os -SECRET = os.getenv("SECRET") +from athena.helpers.list_deployments import list_deployments + PRODUCTION = os.environ.get("PRODUCTION", "0") == "1" -DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///../data/data.sqlite") \ No newline at end of file +DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///../data/data.sqlite") + +DEPLOYMENT_SECRETS = {} +for deployment in list_deployments(): + secret = os.environ.get(f"{deployment.name.upper()}_SECRET") + if secret is None and PRODUCTION: + raise ValueError(f"Missing secret for Artemis deployment {deployment.name}. " + f"Set the {deployment.name.upper()}_SECRET environment variable.") + DEPLOYMENT_SECRETS[deployment.name] = secret diff --git a/athena/athena/helpers/list_deployments.py b/athena/athena/helpers/list_deployments.py new file mode 100644 index 000000000..1f66c0323 --- /dev/null +++ b/athena/athena/helpers/list_deployments.py @@ -0,0 +1,22 @@ +import configparser + +from typing import List, cast +from pathlib import Path + +from pydantic import AnyHttpUrl + +from ..schemas.deployment import Deployment + + +def list_deployments() -> List[Deployment]: + """Get a list of all Artemis instances that Athena should support.""" + deployments_config = configparser.ConfigParser() + deployments_config.read(Path(__file__).parent.parent.parent / "deployments.ini") + return [ + Deployment( + name=deployment, + url=cast(AnyHttpUrl, deployments_config[deployment]["url"]), + artemis_id=deployments_config[deployment]["artemis_id"] + ) + for deployment in deployments_config.sections() + ] diff --git a/athena/athena/schemas/deployment.py b/athena/athena/schemas/deployment.py new file mode 100644 index 000000000..c8c1eee3f --- /dev/null +++ b/athena/athena/schemas/deployment.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel, Field, AnyHttpUrl + +class Deployment(BaseModel): + """An Artemis instance, with the URL and keys.""" + name: str = Field(example="example") + url: AnyHttpUrl = Field(example="https://artemis.cit.tum.de") + artemis_id: str = Field(example="artemis_example") From 88e5ba7c1e48aa66e43cb1f879e84ac88d38f4ba Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 13 May 2024 09:19:22 +0200 Subject: [PATCH 03/49] set values from .env and .ini --- athena/athena/env.py | 4 ++-- athena/athena/helpers/__init__.py | 7 +++++++ athena/athena/{schemas => helpers}/deployment.py | 1 + athena/athena/helpers/list_deployments.py | 2 +- athena/deployments.ini | 3 +++ 5 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 athena/athena/helpers/__init__.py rename athena/athena/{schemas => helpers}/deployment.py (99%) create mode 100644 athena/deployments.ini diff --git a/athena/athena/env.py b/athena/athena/env.py index 1142270a9..56c29fee6 100644 --- a/athena/athena/env.py +++ b/athena/athena/env.py @@ -1,14 +1,14 @@ """Common place for environment variables with sensible defaults for local development.""" import os -from athena.helpers.list_deployments import list_deployments +from athena.helpers import list_deployments PRODUCTION = os.environ.get("PRODUCTION", "0") == "1" DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///../data/data.sqlite") DEPLOYMENT_SECRETS = {} for deployment in list_deployments(): - secret = os.environ.get(f"{deployment.name.upper()}_SECRET") + secret = os.environ.get(f"ARTEMIS_{deployment.name.upper()}_SECRET") if secret is None and PRODUCTION: raise ValueError(f"Missing secret for Artemis deployment {deployment.name}. " f"Set the {deployment.name.upper()}_SECRET environment variable.") diff --git a/athena/athena/helpers/__init__.py b/athena/athena/helpers/__init__.py new file mode 100644 index 000000000..a4b73f33c --- /dev/null +++ b/athena/athena/helpers/__init__.py @@ -0,0 +1,7 @@ +from .list_deployments import list_deployments +from .deployment import Deployment + +__all__ = [ + "list_deployments", + "Deployment" +] \ No newline at end of file diff --git a/athena/athena/schemas/deployment.py b/athena/athena/helpers/deployment.py similarity index 99% rename from athena/athena/schemas/deployment.py rename to athena/athena/helpers/deployment.py index c8c1eee3f..387d4b509 100644 --- a/athena/athena/schemas/deployment.py +++ b/athena/athena/helpers/deployment.py @@ -1,5 +1,6 @@ from pydantic import BaseModel, Field, AnyHttpUrl + class Deployment(BaseModel): """An Artemis instance, with the URL and keys.""" name: str = Field(example="example") diff --git a/athena/athena/helpers/list_deployments.py b/athena/athena/helpers/list_deployments.py index 1f66c0323..db638d9e7 100644 --- a/athena/athena/helpers/list_deployments.py +++ b/athena/athena/helpers/list_deployments.py @@ -5,7 +5,7 @@ from pydantic import AnyHttpUrl -from ..schemas.deployment import Deployment +from .deployment import Deployment def list_deployments() -> List[Deployment]: diff --git a/athena/deployments.ini b/athena/deployments.ini new file mode 100644 index 000000000..3ffd4e454 --- /dev/null +++ b/athena/deployments.ini @@ -0,0 +1,3 @@ +[local] +url = http://localhost:8080/ +artemis_id = local \ No newline at end of file From cfc9f1ff6b2ce681345219ab9eb3315fb0b97b7e Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 19 May 2024 16:33:15 +0200 Subject: [PATCH 04/49] change authorization process --- .../assessment_module_manager/__init__.py | 4 ++ .../assessment_module_manager/authenticate.py | 56 +++++++++++++++++++ .../deployment/__init__.py | 7 +++ .../deployment}/deployment.py | 3 +- .../deployment}/list_deployments.py | 7 +-- .../endpoints/modules_proxy_endpoint.py | 6 +- .../assessment_module_manager/env.py | 12 +++- .../module/request_to_module.py | 8 ++- assessment_module_manager/deployments.ini | 3 + athena/athena/__init__.py | 1 - athena/athena/authenticate.py | 12 ++-- athena/athena/env.py | 12 +--- athena/athena/helpers/__init__.py | 7 --- athena/deployments.ini | 3 - env_example/assessment_module_manager.env | 6 ++ 15 files changed, 107 insertions(+), 40 deletions(-) create mode 100644 assessment_module_manager/assessment_module_manager/authenticate.py create mode 100644 assessment_module_manager/assessment_module_manager/deployment/__init__.py rename {athena/athena/helpers => assessment_module_manager/assessment_module_manager/deployment}/deployment.py (59%) rename {athena/athena/helpers => assessment_module_manager/assessment_module_manager/deployment}/list_deployments.py (69%) create mode 100644 assessment_module_manager/deployments.ini delete mode 100644 athena/deployments.ini diff --git a/assessment_module_manager/assessment_module_manager/__init__.py b/assessment_module_manager/assessment_module_manager/__init__.py index e69de29bb..2f04ff661 100644 --- a/assessment_module_manager/assessment_module_manager/__init__.py +++ b/assessment_module_manager/assessment_module_manager/__init__.py @@ -0,0 +1,4 @@ +import dotenv + +# Load environment variables from .env file (for local development) +dotenv.load_dotenv(override=True) \ No newline at end of file diff --git a/assessment_module_manager/assessment_module_manager/authenticate.py b/assessment_module_manager/assessment_module_manager/authenticate.py new file mode 100644 index 000000000..1b3b6457f --- /dev/null +++ b/assessment_module_manager/assessment_module_manager/authenticate.py @@ -0,0 +1,56 @@ +import inspect +from functools import wraps +from typing import Callable + +from fastapi import HTTPException, Depends +from fastapi.security import APIKeyHeader + +from assessment_module_manager import env +from athena.logger import logger + +api_key_auth_header = APIKeyHeader(name='Authorization', auto_error=False) +api_key_artemis_url_header = APIKeyHeader(name='X-Server-URL', auto_error=False) + + +def verify_artemis_athena_key(artemis_url: str, secret: str): + if artemis_url is None: + raise HTTPException(status_code=401, detail="Invalid Artemis Server Url.") + # cannot proceed even for local development + # database entries cannot be set uniquely + + if artemis_url not in env.DEPLOYMENT_SECRETS or secret != env.DEPLOYMENT_SECRETS[artemis_url]: + if env.PRODUCTION: + raise HTTPException(status_code=401, detail="Invalid API secret.") + logger.warning("DEBUG MODE: Ignoring invalid Artemis Deployment secret.") + + +def authenticated(func: Callable) -> Callable: + """ + Decorator for endpoints that require authentication. + Usage: + @app.post("/endpoint") + @authenticated + def endpoint(): + ... + """ + + @wraps(func) + async def wrapper(*args, secret: str = Depends(api_key_auth_header), + artemis_url: str = Depends(api_key_artemis_url_header), + **kwargs): + verify_artemis_athena_key(artemis_url, secret) # this happens in scope of the ASM Module + if inspect.iscoroutinefunction(func): + return await func(*args, **kwargs) + return func(*args, **kwargs) + + # Update the function signature with the 'secret' parameter, but otherwise keep the annotations intact + sig = inspect.signature(func) + params = list(sig.parameters.values()) + params.append( + inspect.Parameter('secret', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_auth_header))) + params.append(inspect.Parameter('artemis_url', inspect.Parameter.POSITIONAL_OR_KEYWORD, + default=Depends(api_key_artemis_url_header))) + new_sig = sig.replace(parameters=params) + wrapper.__signature__ = new_sig # type: ignore # https://github.com/python/mypy/issues/12472 + + return wrapper diff --git a/assessment_module_manager/assessment_module_manager/deployment/__init__.py b/assessment_module_manager/assessment_module_manager/deployment/__init__.py new file mode 100644 index 000000000..346833b09 --- /dev/null +++ b/assessment_module_manager/assessment_module_manager/deployment/__init__.py @@ -0,0 +1,7 @@ +from .deployment import Deployment +from .list_deployments import list_deployments + +__all__ = [ + "list_deployments", + "Deployment" +] diff --git a/athena/athena/helpers/deployment.py b/assessment_module_manager/assessment_module_manager/deployment/deployment.py similarity index 59% rename from athena/athena/helpers/deployment.py rename to assessment_module_manager/assessment_module_manager/deployment/deployment.py index 387d4b509..779728e52 100644 --- a/athena/athena/helpers/deployment.py +++ b/assessment_module_manager/assessment_module_manager/deployment/deployment.py @@ -4,5 +4,4 @@ class Deployment(BaseModel): """An Artemis instance, with the URL and keys.""" name: str = Field(example="example") - url: AnyHttpUrl = Field(example="https://artemis.cit.tum.de") - artemis_id: str = Field(example="artemis_example") + url: str = Field(example="https://artemis.cit.tum.de") diff --git a/athena/athena/helpers/list_deployments.py b/assessment_module_manager/assessment_module_manager/deployment/list_deployments.py similarity index 69% rename from athena/athena/helpers/list_deployments.py rename to assessment_module_manager/assessment_module_manager/deployment/list_deployments.py index db638d9e7..cf6fa8ab5 100644 --- a/athena/athena/helpers/list_deployments.py +++ b/assessment_module_manager/assessment_module_manager/deployment/list_deployments.py @@ -1,10 +1,8 @@ import configparser -from typing import List, cast +from typing import List from pathlib import Path -from pydantic import AnyHttpUrl - from .deployment import Deployment @@ -15,8 +13,7 @@ def list_deployments() -> List[Deployment]: return [ Deployment( name=deployment, - url=cast(AnyHttpUrl, deployments_config[deployment]["url"]), - artemis_id=deployments_config[deployment]["artemis_id"] + url=deployments_config[deployment]["url"] ) for deployment in deployments_config.sections() ] diff --git a/assessment_module_manager/assessment_module_manager/endpoints/modules_proxy_endpoint.py b/assessment_module_manager/assessment_module_manager/endpoints/modules_proxy_endpoint.py index e94dd1d65..ccd32ea72 100644 --- a/assessment_module_manager/assessment_module_manager/endpoints/modules_proxy_endpoint.py +++ b/assessment_module_manager/assessment_module_manager/endpoints/modules_proxy_endpoint.py @@ -2,7 +2,7 @@ from fastapi import Body, HTTPException, Request from starlette.responses import JSONResponse -from athena.authenticate import authenticated +from assessment_module_manager.authenticate import authenticated from athena.schemas import ExerciseType from assessment_module_manager.app import app from assessment_module_manager.module import ModuleResponse, find_module_by_name, request_to_module @@ -64,11 +64,15 @@ async def proxy_to_module( run_id = request.headers.get('X-Run-ID') if run_id: headers['X-Run-ID'] = run_id + artemis_server_url = request.headers.get('X-Server-URL') + if artemis_server_url: + headers['X-Server-URL'] = artemis_server_url resp = await request_to_module( module, headers, '/' + path, + artemis_server_url, data, method=request.method, ) diff --git a/assessment_module_manager/assessment_module_manager/env.py b/assessment_module_manager/assessment_module_manager/env.py index 2c0327b7f..917f81e46 100644 --- a/assessment_module_manager/assessment_module_manager/env.py +++ b/assessment_module_manager/assessment_module_manager/env.py @@ -1,10 +1,11 @@ """Common place for environment variables with sensible defaults for local development.""" import os +from assessment_module_manager.deployment import list_deployments from assessment_module_manager.module.list_modules import list_modules PRODUCTION = os.environ.get("PRODUCTION", "0") == "1" -SECRET = os.getenv("SECRET") +SECRET = os.getenv("SECRET") # Artemis <-> Athena if SECRET is None: if PRODUCTION == "1": raise ValueError("Missing SECRET environment variable. " @@ -18,3 +19,12 @@ raise ValueError(f"Missing secret for module {module.name}. " f"Set the {module.name.upper()}_SECRET environment variable.") MODULE_SECRETS[module.name] = secret + +DEPLOYMENT_SECRETS = {} +for deployment in list_deployments(): + secret = os.environ.get(f"ARTEMIS_{deployment.name.upper()}_SECRET") + if secret is None and PRODUCTION: + raise ValueError(f"Missing secret for Artemis deployment {deployment.name}. " + f"Set the {deployment.name.upper()}_SECRET environment variable.") + DEPLOYMENT_SECRETS[deployment.url] = secret + diff --git a/assessment_module_manager/assessment_module_manager/module/request_to_module.py b/assessment_module_manager/assessment_module_manager/module/request_to_module.py index 241ea8ff2..22a509c9d 100644 --- a/assessment_module_manager/assessment_module_manager/module/request_to_module.py +++ b/assessment_module_manager/assessment_module_manager/module/request_to_module.py @@ -33,20 +33,22 @@ async def find_module_by_name(module_name: str) -> Optional[Module]: return None -async def request_to_module(module: Module, headers: dict, path: str, data: Optional[dict], method: str) -> ModuleResponse: +async def request_to_module(module: Module, headers: dict, path: str, artemis_url: str, data: Optional[dict], method: str) -> ModuleResponse: """ Helper function to send a request to a module. It raises appropriate FastAPI HTTPException if the request fails. """ module_secret = env.MODULE_SECRETS[module.name] if module_secret: - headers['Authorization'] = module_secret + headers['Authorization'] = module_secret # for inter-Athena communication if module.type == ExerciseType.programming: # We need the Athena secret with the LMS to access repositories. # In order to only have to configure it once for the whole of Athena, # we pass it to the module from here. - headers['X-Repository-Authorization-Secret'] = env.SECRET or "" + headers['X-Repository-Authorization-Secret'] = env.DEPLOYMENT_SECRETS.get(artemis_url, "") + # for repository access + # should be the same as the Artemis key try: async with httpx.AsyncClient(base_url=module.url, timeout=600) as client: diff --git a/assessment_module_manager/deployments.ini b/assessment_module_manager/deployments.ini new file mode 100644 index 000000000..b32e789db --- /dev/null +++ b/assessment_module_manager/deployments.ini @@ -0,0 +1,3 @@ +[local] +url = http://localhost:8080 +artemis_id = local \ No newline at end of file diff --git a/athena/athena/__init__.py b/athena/athena/__init__.py index 8a67e5315..645efceb8 100644 --- a/athena/athena/__init__.py +++ b/athena/athena/__init__.py @@ -8,7 +8,6 @@ from .experiment import get_experiment_environment from .endpoints import submission_selector, submissions_consumer, feedback_consumer, feedback_provider, config_schema_provider, evaluation_provider # type: ignore - @app.get("/") def module_health(): """The root endpoint is used as the health check for the module.""" diff --git a/athena/athena/authenticate.py b/athena/athena/authenticate.py index a37d26469..e6953ad57 100644 --- a/athena/athena/authenticate.py +++ b/athena/athena/authenticate.py @@ -9,11 +9,10 @@ from athena.logger import logger api_key_auth_header = APIKeyHeader(name='Authorization', auto_error=False) -api_key_artemis_id_header = APIKeyHeader(name='...', auto_error=False) -def verify_secret(artemis_id: str, secret: str): - if secret != env.DEPLOYMENT_SECRETS[artemis_id]: +def verify_inter_module_secret_key(secret: str): + if secret != env.ASSESSMENT_MODULE_MANAGER_TO_ATHENA_MODULE_SECRET: if env.PRODUCTION: raise HTTPException(status_code=401, detail="Invalid API secret.") logger.warning("DEBUG MODE: Ignoring invalid API secret.") @@ -30,9 +29,8 @@ def endpoint(): """ @wraps(func) - async def wrapper(*args, secret: str = Depends(api_key_auth_header), - artemis_id: str = Depends(api_key_artemis_id_header), **kwargs): - verify_secret(artemis_id, secret) + async def wrapper(*args, secret: str = Depends(api_key_auth_header), **kwargs): + verify_inter_module_secret_key(secret) # this happens after the ASM Module reissued the request if inspect.iscoroutinefunction(func): return await func(*args, **kwargs) return func(*args, **kwargs) @@ -42,8 +40,6 @@ async def wrapper(*args, secret: str = Depends(api_key_auth_header), params = list(sig.parameters.values()) params.append( inspect.Parameter('secret', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_auth_header))) - params.append(inspect.Parameter('artemis_id', inspect.Parameter.POSITIONAL_OR_KEYWORD, - default=Depends(api_key_artemis_id_header))) new_sig = sig.replace(parameters=params) wrapper.__signature__ = new_sig # type: ignore # https://github.com/python/mypy/issues/12472 diff --git a/athena/athena/env.py b/athena/athena/env.py index 56c29fee6..cca2c6ec2 100644 --- a/athena/athena/env.py +++ b/athena/athena/env.py @@ -1,15 +1,9 @@ """Common place for environment variables with sensible defaults for local development.""" import os -from athena.helpers import list_deployments - PRODUCTION = os.environ.get("PRODUCTION", "0") == "1" DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///../data/data.sqlite") -DEPLOYMENT_SECRETS = {} -for deployment in list_deployments(): - secret = os.environ.get(f"ARTEMIS_{deployment.name.upper()}_SECRET") - if secret is None and PRODUCTION: - raise ValueError(f"Missing secret for Artemis deployment {deployment.name}. " - f"Set the {deployment.name.upper()}_SECRET environment variable.") - DEPLOYMENT_SECRETS[deployment.name] = secret +# a key is needed to authorize a module to +# communicate with the assessment module manager +ASSESSMENT_MODULE_MANAGER_TO_ATHENA_MODULE_SECRET = os.getenv("SECRET") \ No newline at end of file diff --git a/athena/athena/helpers/__init__.py b/athena/athena/helpers/__init__.py index a4b73f33c..e69de29bb 100644 --- a/athena/athena/helpers/__init__.py +++ b/athena/athena/helpers/__init__.py @@ -1,7 +0,0 @@ -from .list_deployments import list_deployments -from .deployment import Deployment - -__all__ = [ - "list_deployments", - "Deployment" -] \ No newline at end of file diff --git a/athena/deployments.ini b/athena/deployments.ini deleted file mode 100644 index 3ffd4e454..000000000 --- a/athena/deployments.ini +++ /dev/null @@ -1,3 +0,0 @@ -[local] -url = http://localhost:8080/ -artemis_id = local \ No newline at end of file diff --git a/env_example/assessment_module_manager.env b/env_example/assessment_module_manager.env index e58571f44..dff63758c 100644 --- a/env_example/assessment_module_manager.env +++ b/env_example/assessment_module_manager.env @@ -7,3 +7,9 @@ MODULE_PROGRAMMING_LLM_SECRET=12345abcdef MODULE_TEXT_LLM_SECRET=12345abcdef MODULE_TEXT_COFEE_SECRET=12345abcdef MODULE_PROGRAMMING_THEMISML_SECRET=12345abcdef + +################################################################ +# Artemis Deployments # +################################################################ +# the deployment name should correspond to the name in deployments.ini +ARTEMIS_DEPLOYMENT_NAME_SECRET=12345abcdef \ No newline at end of file From a376fcb1f0cfc15a8b75dd265d1c4ee3d5eee1c7 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 19 May 2024 20:35:19 +0200 Subject: [PATCH 05/49] extend database to store artemis url --- athena/athena/authenticate.py | 10 +++- athena/athena/contextvars.py | 11 +++++ athena/athena/models/model.py | 4 ++ athena/athena/storage/exercise_storage.py | 32 +++++++++--- athena/athena/storage/feedback_storage.py | 55 +++++++++++++++------ athena/athena/storage/submission_storage.py | 43 ++++++++++++---- 6 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 athena/athena/contextvars.py diff --git a/athena/athena/authenticate.py b/athena/athena/authenticate.py index e6953ad57..6f74af10b 100644 --- a/athena/athena/authenticate.py +++ b/athena/athena/authenticate.py @@ -6,9 +6,12 @@ from fastapi.security import APIKeyHeader from athena import env +from athena.contextvars import set_artemis_url_context_var from athena.logger import logger api_key_auth_header = APIKeyHeader(name='Authorization', auto_error=False) +api_key_artemis_url_header = APIKeyHeader(name='X-Server-URL', auto_error=False) + def verify_inter_module_secret_key(secret: str): @@ -29,8 +32,11 @@ def endpoint(): """ @wraps(func) - async def wrapper(*args, secret: str = Depends(api_key_auth_header), **kwargs): + async def wrapper(*args, secret: str = Depends(api_key_auth_header), + artemis_url: str = Depends(api_key_artemis_url_header), + **kwargs): verify_inter_module_secret_key(secret) # this happens after the ASM Module reissued the request + set_artemis_url_context_var(artemis_url) if inspect.iscoroutinefunction(func): return await func(*args, **kwargs) return func(*args, **kwargs) @@ -40,6 +46,8 @@ async def wrapper(*args, secret: str = Depends(api_key_auth_header), **kwargs): params = list(sig.parameters.values()) params.append( inspect.Parameter('secret', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_auth_header))) + params.append(inspect.Parameter('artemis_url', inspect.Parameter.POSITIONAL_OR_KEYWORD, + default=Depends(api_key_artemis_url_header))) new_sig = sig.replace(parameters=params) wrapper.__signature__ = new_sig # type: ignore # https://github.com/python/mypy/issues/12472 diff --git a/athena/athena/contextvars.py b/athena/athena/contextvars.py new file mode 100644 index 000000000..0aebebf8c --- /dev/null +++ b/athena/athena/contextvars.py @@ -0,0 +1,11 @@ +import contextvars + +artemis_url_context_var = contextvars.ContextVar('artemis_url') + + +def set_artemis_url_context_var(artemis_url: str): + artemis_url_context_var.set(artemis_url) + + +def get_artemis_url(): + return artemis_url_context_var.get() \ No newline at end of file diff --git a/athena/athena/models/model.py b/athena/athena/models/model.py index 77b1ec008..a7ba1a423 100644 --- a/athena/athena/models/model.py +++ b/athena/athena/models/model.py @@ -1,9 +1,13 @@ import importlib from pydantic import BaseModel +from sqlalchemy import Column, String class Model: + + artemis_url = Column(String, primary_key=True, index=True, nullable=False) + @classmethod def get_schema_class(cls) -> BaseModel: # The schema class has the same name as myself, but without the "DB" prefix. diff --git a/athena/athena/storage/exercise_storage.py b/athena/athena/storage/exercise_storage.py index 8d17a5dbb..91b736be1 100644 --- a/athena/athena/storage/exercise_storage.py +++ b/athena/athena/storage/exercise_storage.py @@ -1,37 +1,55 @@ from typing import List, Iterable, Optional, Type +from athena.contextvars import get_artemis_url from athena.database import get_db from athena.schemas import Exercise -def get_stored_exercises(exercise_cls: Type[Exercise], only_ids: Optional[List[int]] = None) -> Iterable[Exercise]: +def get_stored_exercises(exercise_cls: Type[Exercise], artemis_url: str = None, only_ids: Optional[List[int]] = None) -> \ +Iterable[Exercise]: """ Returns a list of exercises for the given exercise type and exercise ids. If only_ids is None, returns all exercises for the given exercise type. """ + + if artemis_url is None: + artemis_url = get_artemis_url() + db_exercise_cls = exercise_cls.get_model_class() with get_db() as db: query = db.query(db_exercise_cls) if only_ids is not None: query = query.filter(db_exercise_cls.id.in_(only_ids)) # type: ignore + query = query.filter(db_exercise_cls.artemis_url == artemis_url) # type: ignore return (e.to_schema() for e in query.all()) -def get_stored_exercise_meta(exercise: Exercise) -> Optional[dict]: +def get_stored_exercise_meta(exercise: Exercise, artemis_url: str = None, ) -> Optional[dict]: """Returns the stored metadata associated with the exercise.""" + + if artemis_url is None: + artemis_url = get_artemis_url() + db_exercise_cls: Type[Exercise] = exercise.__class__.get_model_class() with get_db() as db: - return db.query(db_exercise_cls.meta).filter_by(id=exercise.id).scalar() # type: ignore + return db.query(db_exercise_cls.meta).filter_by(id=exercise.id, + artemis_url=artemis_url).scalar() # type: ignore -def store_exercises(exercises: List[Exercise]): +def store_exercises(exercises: List[Exercise], artemis_url: str = None): """Stores the given exercises, all at once.""" + + if artemis_url is None: + artemis_url = get_artemis_url() + with get_db() as db: for e in exercises: - db.merge(e.to_model()) + exercise_model = e.to_model() + exercise_model.artemis_url = artemis_url + db.merge(exercise_model) db.commit() -def store_exercise(exercise: Exercise): +def store_exercise(exercise: Exercise, artemis_url: str = None): """Stores the given exercise.""" - store_exercises([exercise]) + store_exercises([exercise], artemis_url) diff --git a/athena/athena/storage/feedback_storage.py b/athena/athena/storage/feedback_storage.py index b4b61f668..1daba3eff 100644 --- a/athena/athena/storage/feedback_storage.py +++ b/athena/athena/storage/feedback_storage.py @@ -5,43 +5,57 @@ def get_stored_feedback( - feedback_cls: Type[Feedback], exercise_id: int, submission_id: Union[int, None] + feedback_cls: Type[Feedback], exercise_id: int, submission_id: Union[int, None], artemis_url: str = None ) -> Iterable[Feedback]: """ Returns a list of feedbacks for the given exercise in the given submission. If submission_id is None, returns all feedbacks for the given exercise. """ + + if artemis_url is None: + artemis_url = get_artemis_url() + db_feedback_cls = feedback_cls.get_model_class() with get_db() as db: - query = db.query(db_feedback_cls).filter_by(exercise_id=exercise_id, is_suggestion=0) + query = db.query(db_feedback_cls).filter_by(exercise_id=exercise_id, is_suggestion=0, artemis_url=artemis_url) if submission_id is not None: query = query.filter_by(submission_id=submission_id) return (f.to_schema() for f in query.all()) -def get_stored_feedback_meta(feedback: Feedback) -> Optional[dict]: +def get_stored_feedback_meta(feedback: Feedback, artemis_url: str = None) -> Optional[dict]: """Returns the stored metadata associated with the feedback.""" + + if artemis_url is None: + artemis_url = get_artemis_url() + db_feedback_cls = feedback.__class__.get_model_class() with get_db() as db: - return db.query(db_feedback_cls.meta).filter_by(id=feedback.id).scalar() # type: ignore + return db.query(db_feedback_cls.meta).filter_by(id=feedback.id, + artemis_url=artemis_url).scalar() # type: ignore -def store_feedback(feedback: Feedback, is_lms_id=False) -> Feedback: +def store_feedback(feedback: Feedback, is_lms_id=False, artemis_url: str = None) -> Feedback: """Stores the given LMS feedback. Args: feedback (Feedback): The feedback to store. is_lms_id (bool, optional): Whether the feedback's ID is an LMS ID. Defaults to False. - + artemis_url (str, optional): The URL of the Artemis instance that issued the query Returns: Feedback: The stored feedback with its internal ID assigned. """ + + if artemis_url is None: + artemis_url = get_artemis_url() + db_feedback_cls = feedback.__class__.get_model_class() with get_db() as db: lms_id = None if is_lms_id: lms_id = feedback.id - internal_id = db.query(db_feedback_cls.id).filter_by(lms_id=lms_id).scalar() # type: ignore + internal_id = db.query(db_feedback_cls.id).filter_by(lms_id=lms_id, + artemis_url=artemis_url).scalar() # type: ignore feedback.id = internal_id stored_feedback_model = db.merge(feedback.to_model(lms_id=lms_id)) @@ -50,33 +64,44 @@ def store_feedback(feedback: Feedback, is_lms_id=False) -> Feedback: def get_stored_feedback_suggestions( - feedback_cls: Type[Feedback], exercise_id: int, submission_id: int + feedback_cls: Type[Feedback], exercise_id: int, submission_id: int, artemis_url: str = None ) -> Iterable[Feedback]: """Returns a list of feedback suggestions for the given exercise in the given submission.""" + + if artemis_url is None: + artemis_url = get_artemis_url() + db_feedback_cls = feedback_cls.get_model_class() with get_db() as db: - query = db.query(db_feedback_cls).filter_by(exercise_id=exercise_id, is_suggestion=True) + query = db.query(db_feedback_cls).filter_by(exercise_id=exercise_id, is_suggestion=True, + artemis_url=artemis_url) if submission_id is not None: query = query.filter_by(submission_id=submission_id) return (f.to_schema() for f in query.all()) -def store_feedback_suggestions(feedbacks: List[Feedback]) -> List[Feedback]: +def store_feedback_suggestions(feedbacks: List[Feedback], artemis_url: str = None) -> List[Feedback]: """Stores the given feedbacks as a suggestions. Returns: List[Feedback]: The stored feedback suggestions with their internal IDs assigned. """ + + if artemis_url is None: + artemis_url = get_artemis_url() + stored_feedbacks: List[Feedback] = [] with get_db() as db: for feedback in feedbacks: - stored_feedback_model = db.merge(feedback.to_model(is_suggestion=True)) - db.flush() # Ensure the ID is generated now - stored_feedbacks.append(stored_feedback_model.to_schema()) + feedback_model = feedback.to_model(is_suggestion=True) + feedback_model.artemis_url = artemis_url + feedback_model = db.merge(feedback_model) + db.flush() # Ensure the ID is generated now + stored_feedbacks.append(feedback_model.to_schema()) db.commit() return stored_feedbacks -def store_feedback_suggestion(feedback: Feedback): +def store_feedback_suggestion(feedback: Feedback, artemis_url: str = None): """Stores the given feedback as a suggestion.""" - store_feedback_suggestions([feedback]) + store_feedback_suggestions([feedback], artemis_url) diff --git a/athena/athena/storage/submission_storage.py b/athena/athena/storage/submission_storage.py index 92cfbe739..748699008 100644 --- a/athena/athena/storage/submission_storage.py +++ b/athena/athena/storage/submission_storage.py @@ -1,47 +1,70 @@ from typing import List, Iterable, Union, Type, Optional +from athena.contextvars import get_artemis_url from athena.database import get_db from athena.schemas import Submission def count_stored_submissions( - submission_cls: Type[Submission], exercise_id: int + submission_cls: Type[Submission], exercise_id: int, artemis_url: str = None ) -> int: """Returns the number of submissions for the given exercise.""" + + if artemis_url is None: + artemis_url = get_artemis_url() + db_submission_cls = submission_cls.get_model_class() with get_db() as db: - return db.query(db_submission_cls).filter_by(exercise_id=exercise_id).count() # type: ignore + return db.query(db_submission_cls).filter_by(exercise_id=exercise_id, + artemis_url=artemis_url).count() # type: ignore def get_stored_submissions( - submission_cls: Type[Submission], exercise_id: int, only_ids: Union[List[int], None] = None + submission_cls: Type[Submission], exercise_id: int, only_ids: Union[List[int], None] = None, + artemis_url: str = None ) -> Iterable[Submission]: """ Returns a list of submissions for the given exercise and submission ids. If only_ids is None, returns all submissions for the given exercise. """ + + if artemis_url is None: + artemis_url = get_artemis_url() + db_submission_cls = submission_cls.get_model_class() with get_db() as db: - query = db.query(db_submission_cls).filter_by(exercise_id=exercise_id) + query = db.query(db_submission_cls).filter_by(exercise_id=exercise_id, artemis_url=artemis_url) if only_ids is not None: query = query.filter(db_submission_cls.id.in_(only_ids)) # type: ignore return (s.to_schema() for s in query.all()) -def get_stored_submission_meta(submission: Submission) -> Optional[dict]: +def get_stored_submission_meta(submission: Submission, artemis_url: str = None) -> Optional[dict]: """Returns the stored metadata associated with the submission.""" + + if artemis_url is None: + artemis_url = get_artemis_url() + db_submission_cls = submission.__class__.get_model_class() with get_db() as db: - return db.query(db_submission_cls.meta).filter_by(id=submission.id).scalar() # type: ignore + return db.query(db_submission_cls.meta).filter_by(id=submission.id, + artemis_url=artemis_url).scalar() # type: ignore -def store_submissions(submissions: List[Submission]): +def store_submissions(submissions: List[Submission], artemis_url: str = None): """Stores the given submissions, all at once.""" + + if artemis_url is None: + artemis_url = get_artemis_url() + with get_db() as db: for s in submissions: - db.merge(s.to_model()) + submission_model = s.to_model() + submission_model.artemis_url = artemis_url + db.merge(submission_model) db.commit() -def store_submission(submission: Submission): + +def store_submission(submission: Submission, artemis_url: str = None): """Stores the given submission.""" - store_submissions([submission]) + store_submissions([submission], artemis_url) From d3567491f122e2f103ccd31b57bb95d440ca0d1b Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 19 May 2024 21:25:22 +0200 Subject: [PATCH 06/49] fix bugs --- athena/athena/endpoints.py | 4 ++-- athena/athena/models/model.py | 8 ++++++-- athena/athena/storage/feedback_storage.py | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/athena/athena/endpoints.py b/athena/athena/endpoints.py index a610b15a1..6d259a2a2 100644 --- a/athena/athena/endpoints.py +++ b/athena/athena/endpoints.py @@ -311,7 +311,7 @@ def feedback_provider(func: Union[ async def wrapper( exercise: exercise_type, submission: submission_type, - is_graded: is_graded_type = Body(True), + isGraded: is_graded_type = Body(True, alias="isGraded"), module_config: module_config_type = Depends(get_dynamic_module_config_factory(module_config_type))): # Retrieve existing metadata for the exercise, submission and feedback @@ -326,7 +326,7 @@ async def wrapper( kwargs["module_config"] = module_config if "is_graded" in inspect.signature(func).parameters: - kwargs["is_graded"] = is_graded + kwargs["is_graded"] = isGraded # Call the actual provider if inspect.iscoroutinefunction(func): diff --git a/athena/athena/models/model.py b/athena/athena/models/model.py index a7ba1a423..b6a73cea3 100644 --- a/athena/athena/models/model.py +++ b/athena/athena/models/model.py @@ -1,12 +1,16 @@ import importlib from pydantic import BaseModel -from sqlalchemy import Column, String +from sqlalchemy import Column, String, UniqueConstraint class Model: - artemis_url = Column(String, primary_key=True, index=True, nullable=False) + artemis_url = Column(String, index=True, nullable=False) + + __table_args__ = ( + UniqueConstraint('id', 'artemis_url', name='uix_id_artemis_url'), + ) @classmethod def get_schema_class(cls) -> BaseModel: diff --git a/athena/athena/storage/feedback_storage.py b/athena/athena/storage/feedback_storage.py index 1daba3eff..3192e8729 100644 --- a/athena/athena/storage/feedback_storage.py +++ b/athena/athena/storage/feedback_storage.py @@ -1,5 +1,6 @@ from typing import Iterable, Union, Type, Optional, List +from athena.contextvars import get_artemis_url from athena.database import get_db from athena.schemas import Feedback From 7df5183007a5c6d56584c42eaa691cc83f0e40f1 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 19 May 2024 22:50:59 +0200 Subject: [PATCH 07/49] fix linting issues --- .../assessment_module_manager/env.py | 1 - athena/athena/__init__.py | 6 ++++-- athena/athena/contextvars.py | 18 +++++++++++++++-- .../helpers/programming/code_repository.py | 7 +++---- .../repository_authorization_middleware.py | 9 ++++----- athena/athena/storage/exercise_storage.py | 8 ++++---- athena/athena/storage/feedback_storage.py | 20 +++++++++---------- athena/athena/storage/submission_storage.py | 14 ++++++------- 8 files changed, 48 insertions(+), 35 deletions(-) diff --git a/assessment_module_manager/assessment_module_manager/env.py b/assessment_module_manager/assessment_module_manager/env.py index 917f81e46..f52fb9ff7 100644 --- a/assessment_module_manager/assessment_module_manager/env.py +++ b/assessment_module_manager/assessment_module_manager/env.py @@ -27,4 +27,3 @@ raise ValueError(f"Missing secret for Artemis deployment {deployment.name}. " f"Set the {deployment.name.upper()}_SECRET environment variable.") DEPLOYMENT_SECRETS[deployment.url] = secret - diff --git a/athena/athena/__init__.py b/athena/athena/__init__.py index 645efceb8..76841e471 100644 --- a/athena/athena/__init__.py +++ b/athena/athena/__init__.py @@ -2,6 +2,7 @@ import runpy from pathlib import Path +from . import contextvars from .app import app from .schemas import ExerciseType, GradingCriterion, StructuredGradingInstruction from .metadata import emit_meta, get_meta @@ -22,6 +23,8 @@ def run_module(): __all__ = [ + "contextvars", + "app", "submission_selector", "submissions_consumer", "feedback_consumer", @@ -33,6 +36,5 @@ def run_module(): "get_experiment_environment", "ExerciseType", "GradingCriterion", - "StructuredGradingInstruction", - "app" + "StructuredGradingInstruction" ] diff --git a/athena/athena/contextvars.py b/athena/athena/contextvars.py index 0aebebf8c..fea5650cc 100644 --- a/athena/athena/contextvars.py +++ b/athena/athena/contextvars.py @@ -1,6 +1,8 @@ import contextvars -artemis_url_context_var = contextvars.ContextVar('artemis_url') +artemis_url_context_var: contextvars.ContextVar = contextvars.ContextVar('artemis_url') +repository_authorization_secret_context_var: contextvars.ContextVar = contextvars.ContextVar( + 'repository_authorization_secret') def set_artemis_url_context_var(artemis_url: str): @@ -8,4 +10,16 @@ def set_artemis_url_context_var(artemis_url: str): def get_artemis_url(): - return artemis_url_context_var.get() \ No newline at end of file + return artemis_url_context_var.get() + + +def set_repository_authorization_secret_context_var(repository_authorization_secret: str): + repository_authorization_secret_context_var.set(repository_authorization_secret) + + +def get_repository_authorization_secret_context_var(): + return repository_authorization_secret_context_var.get() + + +def repository_authorization_secret_context_var_empty(): + return repository_authorization_secret_context_var.get(None) is None diff --git a/athena/athena/helpers/programming/code_repository.py b/athena/athena/helpers/programming/code_repository.py index fe314cb19..31a1675ef 100644 --- a/athena/athena/helpers/programming/code_repository.py +++ b/athena/athena/helpers/programming/code_repository.py @@ -4,7 +4,7 @@ from typing import Optional, cast from zipfile import ZipFile -import athena # for importing athena.app (which is not directly possible because of circular imports) +from athena import contextvars import httpx from git.repo import Repo @@ -24,10 +24,9 @@ def get_repository_zip(url: str, authorization_secret: Optional[str] = None) -> if not cache_file_path.exists(): if authorization_secret is None: - # auto-determine from FastAPI app state - if athena.app.state.repository_authorization_secret is None: + if contextvars.repository_authorization_secret_context_var_empty(): raise ValueError("Authorization secret for the repository API is not set. Pass authorization_secret to this function or add the X-Repository-Authorization-Secret header to the request from the assessment module manager.") - authorization_secret = athena.app.state.repository_authorization_secret + authorization_secret = contextvars.get_repository_authorization_secret_context_var() with httpx.stream("GET", url, headers={ "Authorization": cast(str, authorization_secret) }) as response: response.raise_for_status() with open(cache_file_path, "wb") as f: diff --git a/athena/athena/helpers/programming/repository_authorization_middleware.py b/athena/athena/helpers/programming/repository_authorization_middleware.py index 9e328e4b0..3208521f7 100644 --- a/athena/athena/helpers/programming/repository_authorization_middleware.py +++ b/athena/athena/helpers/programming/repository_authorization_middleware.py @@ -2,6 +2,9 @@ from starlette.middleware.base import BaseHTTPMiddleware from typing import Callable, Awaitable +from athena.contextvars import set_repository_authorization_secret_context_var + + class RepositoryAuthorizationMiddleware(BaseHTTPMiddleware): """ Capture the X-Repository-Authorization-Secret header from the assessment module manager and store it in the app state. @@ -14,7 +17,7 @@ async def dispatch( x_repository_auth_secret = request.headers.get("X-Repository-Authorization-Secret") if x_repository_auth_secret: - request.app.state.repository_authorization_secret = x_repository_auth_secret + set_repository_authorization_secret_context_var(x_repository_auth_secret) response = await call_next(request) return response @@ -22,7 +25,3 @@ async def dispatch( def init_repo_auth_middleware(app: FastAPI): app.add_middleware(RepositoryAuthorizationMiddleware) - - def init_repo_auth_secret(): - app.state.repository_authorization_secret = None - app.on_event("startup")(init_repo_auth_secret) diff --git a/athena/athena/storage/exercise_storage.py b/athena/athena/storage/exercise_storage.py index 91b736be1..dce29ecf7 100644 --- a/athena/athena/storage/exercise_storage.py +++ b/athena/athena/storage/exercise_storage.py @@ -5,7 +5,7 @@ from athena.schemas import Exercise -def get_stored_exercises(exercise_cls: Type[Exercise], artemis_url: str = None, only_ids: Optional[List[int]] = None) -> \ +def get_stored_exercises(exercise_cls: Type[Exercise], artemis_url: Optional[str] = None, only_ids: Optional[List[int]] = None) -> \ Iterable[Exercise]: """ Returns a list of exercises for the given exercise type and exercise ids. @@ -24,7 +24,7 @@ def get_stored_exercises(exercise_cls: Type[Exercise], artemis_url: str = None, return (e.to_schema() for e in query.all()) -def get_stored_exercise_meta(exercise: Exercise, artemis_url: str = None, ) -> Optional[dict]: +def get_stored_exercise_meta(exercise: Exercise, artemis_url: Optional[str] = None, ) -> Optional[dict]: """Returns the stored metadata associated with the exercise.""" if artemis_url is None: @@ -36,7 +36,7 @@ def get_stored_exercise_meta(exercise: Exercise, artemis_url: str = None, ) -> O artemis_url=artemis_url).scalar() # type: ignore -def store_exercises(exercises: List[Exercise], artemis_url: str = None): +def store_exercises(exercises: List[Exercise], artemis_url: Optional[str] = None): """Stores the given exercises, all at once.""" if artemis_url is None: @@ -50,6 +50,6 @@ def store_exercises(exercises: List[Exercise], artemis_url: str = None): db.commit() -def store_exercise(exercise: Exercise, artemis_url: str = None): +def store_exercise(exercise: Exercise, artemis_url: Optional[str] = None): """Stores the given exercise.""" store_exercises([exercise], artemis_url) diff --git a/athena/athena/storage/feedback_storage.py b/athena/athena/storage/feedback_storage.py index 3192e8729..3bd2ddd1e 100644 --- a/athena/athena/storage/feedback_storage.py +++ b/athena/athena/storage/feedback_storage.py @@ -6,7 +6,7 @@ def get_stored_feedback( - feedback_cls: Type[Feedback], exercise_id: int, submission_id: Union[int, None], artemis_url: str = None + feedback_cls: Type[Feedback], exercise_id: int, submission_id: Union[int, None], artemis_url: Optional[str] = None ) -> Iterable[Feedback]: """ Returns a list of feedbacks for the given exercise in the given submission. @@ -24,7 +24,7 @@ def get_stored_feedback( return (f.to_schema() for f in query.all()) -def get_stored_feedback_meta(feedback: Feedback, artemis_url: str = None) -> Optional[dict]: +def get_stored_feedback_meta(feedback: Feedback, artemis_url: Optional[str] = None) -> Optional[dict]: """Returns the stored metadata associated with the feedback.""" if artemis_url is None: @@ -32,11 +32,11 @@ def get_stored_feedback_meta(feedback: Feedback, artemis_url: str = None) -> Opt db_feedback_cls = feedback.__class__.get_model_class() with get_db() as db: - return db.query(db_feedback_cls.meta).filter_by(id=feedback.id, - artemis_url=artemis_url).scalar() # type: ignore + return db.query(db_feedback_cls.meta).filter_by(id=feedback.id, # type: ignore + artemis_url=artemis_url).scalar() -def store_feedback(feedback: Feedback, is_lms_id=False, artemis_url: str = None) -> Feedback: +def store_feedback(feedback: Feedback, is_lms_id=False, artemis_url: Optional[str] = None) -> Feedback: """Stores the given LMS feedback. Args: @@ -55,8 +55,8 @@ def store_feedback(feedback: Feedback, is_lms_id=False, artemis_url: str = None) lms_id = None if is_lms_id: lms_id = feedback.id - internal_id = db.query(db_feedback_cls.id).filter_by(lms_id=lms_id, - artemis_url=artemis_url).scalar() # type: ignore + internal_id = db.query(db_feedback_cls.id).filter_by(lms_id=lms_id, # type: ignore + artemis_url=artemis_url).scalar() feedback.id = internal_id stored_feedback_model = db.merge(feedback.to_model(lms_id=lms_id)) @@ -65,7 +65,7 @@ def store_feedback(feedback: Feedback, is_lms_id=False, artemis_url: str = None) def get_stored_feedback_suggestions( - feedback_cls: Type[Feedback], exercise_id: int, submission_id: int, artemis_url: str = None + feedback_cls: Type[Feedback], exercise_id: int, submission_id: int, artemis_url: Optional[str] = None ) -> Iterable[Feedback]: """Returns a list of feedback suggestions for the given exercise in the given submission.""" @@ -81,7 +81,7 @@ def get_stored_feedback_suggestions( return (f.to_schema() for f in query.all()) -def store_feedback_suggestions(feedbacks: List[Feedback], artemis_url: str = None) -> List[Feedback]: +def store_feedback_suggestions(feedbacks: List[Feedback], artemis_url: Optional[str] = None) -> List[Feedback]: """Stores the given feedbacks as a suggestions. Returns: @@ -103,6 +103,6 @@ def store_feedback_suggestions(feedbacks: List[Feedback], artemis_url: str = Non return stored_feedbacks -def store_feedback_suggestion(feedback: Feedback, artemis_url: str = None): +def store_feedback_suggestion(feedback: Feedback, artemis_url: Optional[str] = None): """Stores the given feedback as a suggestion.""" store_feedback_suggestions([feedback], artemis_url) diff --git a/athena/athena/storage/submission_storage.py b/athena/athena/storage/submission_storage.py index 748699008..a6667d7a1 100644 --- a/athena/athena/storage/submission_storage.py +++ b/athena/athena/storage/submission_storage.py @@ -6,7 +6,7 @@ def count_stored_submissions( - submission_cls: Type[Submission], exercise_id: int, artemis_url: str = None + submission_cls: Type[Submission], exercise_id: int, artemis_url: Optional[str] = None ) -> int: """Returns the number of submissions for the given exercise.""" @@ -21,7 +21,7 @@ def count_stored_submissions( def get_stored_submissions( submission_cls: Type[Submission], exercise_id: int, only_ids: Union[List[int], None] = None, - artemis_url: str = None + artemis_url: Optional[str] = None ) -> Iterable[Submission]: """ Returns a list of submissions for the given exercise and submission ids. @@ -39,7 +39,7 @@ def get_stored_submissions( return (s.to_schema() for s in query.all()) -def get_stored_submission_meta(submission: Submission, artemis_url: str = None) -> Optional[dict]: +def get_stored_submission_meta(submission: Submission, artemis_url: Optional[str] = None) -> Optional[dict]: """Returns the stored metadata associated with the submission.""" if artemis_url is None: @@ -47,11 +47,11 @@ def get_stored_submission_meta(submission: Submission, artemis_url: str = None) db_submission_cls = submission.__class__.get_model_class() with get_db() as db: - return db.query(db_submission_cls.meta).filter_by(id=submission.id, - artemis_url=artemis_url).scalar() # type: ignore + return db.query(db_submission_cls.meta).filter_by(id=submission.id, # type: ignore + artemis_url=artemis_url).scalar() -def store_submissions(submissions: List[Submission], artemis_url: str = None): +def store_submissions(submissions: List[Submission], artemis_url: Optional[str] = None): """Stores the given submissions, all at once.""" if artemis_url is None: @@ -65,6 +65,6 @@ def store_submissions(submissions: List[Submission], artemis_url: str = None): db.commit() -def store_submission(submission: Submission, artemis_url: str = None): +def store_submission(submission: Submission, artemis_url: Optional[str] = None): """Stores the given submission.""" store_submissions([submission], artemis_url) From 6f837a193b0af553971406c4b19a13eadafce1fd Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 19 May 2024 23:00:21 +0200 Subject: [PATCH 08/49] add further deployments --- assessment_module_manager/deployments.ini | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assessment_module_manager/deployments.ini b/assessment_module_manager/deployments.ini index b32e789db..c70367e91 100644 --- a/assessment_module_manager/deployments.ini +++ b/assessment_module_manager/deployments.ini @@ -1,3 +1,8 @@ [local] url = http://localhost:8080 -artemis_id = local \ No newline at end of file + +[ma-schwind] +url = https://ma-schwind.ase.cit.tum.de + +[ts4] +url = https://artemis-test4.artemis.cit.tum.de \ No newline at end of file From 749aeaef4939192a89da1bf00f9f8e32b03424ab Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 19 May 2024 23:32:10 +0200 Subject: [PATCH 09/49] remove dotenv --- .../assessment_module_manager/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/assessment_module_manager/assessment_module_manager/__init__.py b/assessment_module_manager/assessment_module_manager/__init__.py index 2f04ff661..e69de29bb 100644 --- a/assessment_module_manager/assessment_module_manager/__init__.py +++ b/assessment_module_manager/assessment_module_manager/__init__.py @@ -1,4 +0,0 @@ -import dotenv - -# Load environment variables from .env file (for local development) -dotenv.load_dotenv(override=True) \ No newline at end of file From cf8bd83af7115dbb5889c35ec9969522674a88ca Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 19 May 2024 23:43:15 +0200 Subject: [PATCH 10/49] remove dotenv and unused code --- .../assessment_module_manager/env.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/assessment_module_manager/assessment_module_manager/env.py b/assessment_module_manager/assessment_module_manager/env.py index f52fb9ff7..a9816d803 100644 --- a/assessment_module_manager/assessment_module_manager/env.py +++ b/assessment_module_manager/assessment_module_manager/env.py @@ -5,12 +5,6 @@ from assessment_module_manager.module.list_modules import list_modules PRODUCTION = os.environ.get("PRODUCTION", "0") == "1" -SECRET = os.getenv("SECRET") # Artemis <-> Athena -if SECRET is None: - if PRODUCTION == "1": - raise ValueError("Missing SECRET environment variable. " - "Set it to a random string to secure the communication between the LMS and the assessment module manager.") - SECRET = "abcdef12345" # noqa: This secret is only used for development setups for simplicity MODULE_SECRETS = {} for module in list_modules(): @@ -25,5 +19,7 @@ secret = os.environ.get(f"ARTEMIS_{deployment.name.upper()}_SECRET") if secret is None and PRODUCTION: raise ValueError(f"Missing secret for Artemis deployment {deployment.name}. " - f"Set the {deployment.name.upper()}_SECRET environment variable.") + f"Set the {deployment.name.upper()}_SECRET environment variable to secure the communication " + f"between the LMS and the assessment module manager.") + secret = "abcdef12345" # noqa: This secret is only used for development setups for simplicity DEPLOYMENT_SECRETS[deployment.url] = secret From 9348bf3577c41d66da92074a89f312204adf2b44 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 20 May 2024 00:51:50 +0200 Subject: [PATCH 11/49] adjust deployment units --- assessment_module_manager/Dockerfile | 3 +++ assessment_module_manager/deployments.docker.ini | 2 ++ assessment_module_manager/deployments.ini | 10 +++------- 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 assessment_module_manager/deployments.docker.ini diff --git a/assessment_module_manager/Dockerfile b/assessment_module_manager/Dockerfile index c43cb0a16..2e68fd209 100644 --- a/assessment_module_manager/Dockerfile +++ b/assessment_module_manager/Dockerfile @@ -28,5 +28,8 @@ COPY . ./ # Use modules configuration file for docker RUN mv ./modules.docker.ini ./modules.ini +# Use deployments configuration file for docker +RUN mv ./deployments.docker.ini ./deployments.ini + # poetry scripts don't work here CMD poetry run python -m assessment_module_manager \ No newline at end of file diff --git a/assessment_module_manager/deployments.docker.ini b/assessment_module_manager/deployments.docker.ini new file mode 100644 index 000000000..9a2e3afa4 --- /dev/null +++ b/assessment_module_manager/deployments.docker.ini @@ -0,0 +1,2 @@ +[ma-schwind] +url = https://ma-schwind.ase.cit.tum.de \ No newline at end of file diff --git a/assessment_module_manager/deployments.ini b/assessment_module_manager/deployments.ini index c70367e91..1c55f4614 100644 --- a/assessment_module_manager/deployments.ini +++ b/assessment_module_manager/deployments.ini @@ -1,8 +1,4 @@ -[local] -url = http://localhost:8080 - -[ma-schwind] -url = https://ma-schwind.ase.cit.tum.de +# for local development only -[ts4] -url = https://artemis-test4.artemis.cit.tum.de \ No newline at end of file +[local] +url = http://localhost:8080 \ No newline at end of file From f01435925696b6262e55e45202fb03d51ca91b3a Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 20 May 2024 01:09:31 +0200 Subject: [PATCH 12/49] make deployments not compulsory --- .../assessment_module_manager/env.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/assessment_module_manager/assessment_module_manager/env.py b/assessment_module_manager/assessment_module_manager/env.py index a9816d803..f52cf3043 100644 --- a/assessment_module_manager/assessment_module_manager/env.py +++ b/assessment_module_manager/assessment_module_manager/env.py @@ -3,6 +3,7 @@ from assessment_module_manager.deployment import list_deployments from assessment_module_manager.module.list_modules import list_modules +from assessment_module_manager.logger import logger PRODUCTION = os.environ.get("PRODUCTION", "0") == "1" @@ -18,8 +19,9 @@ for deployment in list_deployments(): secret = os.environ.get(f"ARTEMIS_{deployment.name.upper()}_SECRET") if secret is None and PRODUCTION: - raise ValueError(f"Missing secret for Artemis deployment {deployment.name}. " - f"Set the {deployment.name.upper()}_SECRET environment variable to secure the communication " - f"between the LMS and the assessment module manager.") + logger.warning("Missing secret for Artemis deployment %s. " + "Set the %s_SECRET environment variable to secure the communication " + "between the LMS and the assessment module manager.", + deployment.name, deployment.name.upper()) secret = "abcdef12345" # noqa: This secret is only used for development setups for simplicity DEPLOYMENT_SECRETS[deployment.url] = secret From 76df5d9a811ef039ff213390d218ce9556b11031 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 20 May 2024 02:00:02 +0200 Subject: [PATCH 13/49] bugs --- assessment_module_manager/assessment_module_manager/env.py | 2 +- assessment_module_manager/deployments.docker.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assessment_module_manager/assessment_module_manager/env.py b/assessment_module_manager/assessment_module_manager/env.py index f52cf3043..1fe5a7461 100644 --- a/assessment_module_manager/assessment_module_manager/env.py +++ b/assessment_module_manager/assessment_module_manager/env.py @@ -20,7 +20,7 @@ secret = os.environ.get(f"ARTEMIS_{deployment.name.upper()}_SECRET") if secret is None and PRODUCTION: logger.warning("Missing secret for Artemis deployment %s. " - "Set the %s_SECRET environment variable to secure the communication " + "Set the ARTEMIS_%s_SECRET environment variable to secure the communication " "between the LMS and the assessment module manager.", deployment.name, deployment.name.upper()) secret = "abcdef12345" # noqa: This secret is only used for development setups for simplicity diff --git a/assessment_module_manager/deployments.docker.ini b/assessment_module_manager/deployments.docker.ini index 9a2e3afa4..64f60b3ea 100644 --- a/assessment_module_manager/deployments.docker.ini +++ b/assessment_module_manager/deployments.docker.ini @@ -1,2 +1,2 @@ -[ma-schwind] +[schwind] url = https://ma-schwind.ase.cit.tum.de \ No newline at end of file From bc293fa69d0f67d75454d68e7192f4c9ff527753 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 20 May 2024 02:58:47 +0200 Subject: [PATCH 14/49] more logging --- .../assessment_module_manager/authenticate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assessment_module_manager/assessment_module_manager/authenticate.py b/assessment_module_manager/assessment_module_manager/authenticate.py index 1b3b6457f..add8dcf44 100644 --- a/assessment_module_manager/assessment_module_manager/authenticate.py +++ b/assessment_module_manager/assessment_module_manager/authenticate.py @@ -13,11 +13,13 @@ def verify_artemis_athena_key(artemis_url: str, secret: str): + logger.info("Received a request from: %s", artemis_url) if artemis_url is None: raise HTTPException(status_code=401, detail="Invalid Artemis Server Url.") # cannot proceed even for local development # database entries cannot be set uniquely + logger.info("Key found: ", artemis_url in env.DEPLOYMENT_SECRETS) if artemis_url not in env.DEPLOYMENT_SECRETS or secret != env.DEPLOYMENT_SECRETS[artemis_url]: if env.PRODUCTION: raise HTTPException(status_code=401, detail="Invalid API secret.") From 61dfd0a21197410b439b4bf28f60811e938c12fd Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 20 May 2024 04:02:10 +0200 Subject: [PATCH 15/49] more logging --- .../assessment_module_manager/authenticate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assessment_module_manager/assessment_module_manager/authenticate.py b/assessment_module_manager/assessment_module_manager/authenticate.py index add8dcf44..b39cbe31b 100644 --- a/assessment_module_manager/assessment_module_manager/authenticate.py +++ b/assessment_module_manager/assessment_module_manager/authenticate.py @@ -19,7 +19,7 @@ def verify_artemis_athena_key(artemis_url: str, secret: str): # cannot proceed even for local development # database entries cannot be set uniquely - logger.info("Key found: ", artemis_url in env.DEPLOYMENT_SECRETS) + logger.info("Key 1 %s key 2 %s", secret, env.DEPLOYMENT_SECRETS[artemis_url]) if artemis_url not in env.DEPLOYMENT_SECRETS or secret != env.DEPLOYMENT_SECRETS[artemis_url]: if env.PRODUCTION: raise HTTPException(status_code=401, detail="Invalid API secret.") From 92b542469fa2e6b62d944651eb2408b0e905560a Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 20 May 2024 04:13:49 +0200 Subject: [PATCH 16/49] fix bug --- .../assessment_module_manager/authenticate.py | 2 -- assessment_module_manager/assessment_module_manager/env.py | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/assessment_module_manager/assessment_module_manager/authenticate.py b/assessment_module_manager/assessment_module_manager/authenticate.py index b39cbe31b..1b3b6457f 100644 --- a/assessment_module_manager/assessment_module_manager/authenticate.py +++ b/assessment_module_manager/assessment_module_manager/authenticate.py @@ -13,13 +13,11 @@ def verify_artemis_athena_key(artemis_url: str, secret: str): - logger.info("Received a request from: %s", artemis_url) if artemis_url is None: raise HTTPException(status_code=401, detail="Invalid Artemis Server Url.") # cannot proceed even for local development # database entries cannot be set uniquely - logger.info("Key 1 %s key 2 %s", secret, env.DEPLOYMENT_SECRETS[artemis_url]) if artemis_url not in env.DEPLOYMENT_SECRETS or secret != env.DEPLOYMENT_SECRETS[artemis_url]: if env.PRODUCTION: raise HTTPException(status_code=401, detail="Invalid API secret.") diff --git a/assessment_module_manager/assessment_module_manager/env.py b/assessment_module_manager/assessment_module_manager/env.py index 1fe5a7461..17e279cb5 100644 --- a/assessment_module_manager/assessment_module_manager/env.py +++ b/assessment_module_manager/assessment_module_manager/env.py @@ -23,5 +23,6 @@ "Set the ARTEMIS_%s_SECRET environment variable to secure the communication " "between the LMS and the assessment module manager.", deployment.name, deployment.name.upper()) - secret = "abcdef12345" # noqa: This secret is only used for development setups for simplicity + if secret is None and not PRODUCTION: + secret = "abcdef12345" # noqa: This secret is only used for development setups for simplicity DEPLOYMENT_SECRETS[deployment.url] = secret From 2db5fa90dc870b3484394cdd5830b13153a6097e Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 20 May 2024 04:41:06 +0200 Subject: [PATCH 17/49] rename unique constraint --- athena/athena/models/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/athena/athena/models/model.py b/athena/athena/models/model.py index b6a73cea3..16fb8580e 100644 --- a/athena/athena/models/model.py +++ b/athena/athena/models/model.py @@ -9,7 +9,7 @@ class Model: artemis_url = Column(String, index=True, nullable=False) __table_args__ = ( - UniqueConstraint('id', 'artemis_url', name='uix_id_artemis_url'), + UniqueConstraint('id', 'artemis_url'), ) @classmethod From c95634f1c4e9a66b33f09a068cb79c07aae08dc7 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka <33299157+undernagruzez@users.noreply.github.com> Date: Mon, 27 May 2024 11:17:25 +0200 Subject: [PATCH 18/49] Update __main__.py --- assessment_module_manager/assessment_module_manager/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assessment_module_manager/assessment_module_manager/__main__.py b/assessment_module_manager/assessment_module_manager/__main__.py index 9c4b8d5fa..aef21affc 100644 --- a/assessment_module_manager/assessment_module_manager/__main__.py +++ b/assessment_module_manager/assessment_module_manager/__main__.py @@ -22,7 +22,7 @@ def main(): uvicorn.run("assessment_module_manager.__main__:app", host="0.0.0.0", port=5000) else: logger.warning("Running in DEVELOPMENT mode") - uvicorn.run("assessment_module_manager.__main__:app", host="127.0.0.1", port=5000, reload=True) + uvicorn.run("assessment_module_manager.__main__:app", host="0.0.0.0", port=5000, reload=True) # Add things to __all__ just to mark them as important to import From 1f552cc8a19c9e67c1482e29743bca9a38f5b8b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 20:52:54 +0000 Subject: [PATCH 19/49] Bump mysql2 from 3.9.7 to 3.9.8 in /playground Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 3.9.7 to 3.9.8. - [Release notes](https://github.com/sidorares/node-mysql2/releases) - [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md) - [Commits](https://github.com/sidorares/node-mysql2/compare/v3.9.7...v3.9.8) --- updated-dependencies: - dependency-name: mysql2 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- playground/package-lock.json | 8 ++++---- playground/package.json | 2 +- playground/yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/playground/package-lock.json b/playground/package-lock.json index 310d9a087..3b7e27631 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -49,7 +49,7 @@ "eslint": "8.52.0", "eslint-config-next": "13.5.6", "inquirer": "9.2.11", - "mysql2": "3.9.7", + "mysql2": "3.9.8", "typescript": "5.2.2" } }, @@ -5732,9 +5732,9 @@ } }, "node_modules/mysql2": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.7.tgz", - "integrity": "sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==", + "version": "3.9.8", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.8.tgz", + "integrity": "sha512-+5JKNjPuks1FNMoy9TYpl77f+5frbTklz7eb3XDwbpsERRLEeXiW2PDEkakYF50UuKU2qwfGnyXpKYvukv8mGA==", "dev": true, "dependencies": { "denque": "^2.1.0", diff --git a/playground/package.json b/playground/package.json index 1258acf51..ab26b7703 100644 --- a/playground/package.json +++ b/playground/package.json @@ -54,7 +54,7 @@ "eslint": "8.52.0", "eslint-config-next": "13.5.6", "inquirer": "9.2.11", - "mysql2": "3.9.7", + "mysql2": "3.9.8", "typescript": "5.2.2" } } diff --git a/playground/yarn.lock b/playground/yarn.lock index 74f83b3d7..27aac9489 100644 --- a/playground/yarn.lock +++ b/playground/yarn.lock @@ -3260,10 +3260,10 @@ mute-stream@1.0.0: resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== -mysql2@3.9.7: - version "3.9.7" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.9.7.tgz#843755daf65b5ef08afe545fe14b8fb62824741a" - integrity sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw== +mysql2@3.9.8: + version "3.9.8" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.9.8.tgz#fe8a0f975f2c495ed76ca988ddc5505801dc49ce" + integrity sha512-+5JKNjPuks1FNMoy9TYpl77f+5frbTklz7eb3XDwbpsERRLEeXiW2PDEkakYF50UuKU2qwfGnyXpKYvukv8mGA== dependencies: denque "^2.1.0" generate-function "^2.3.1" From af34b897b5eacf6558292e5de68580efe56f1f04 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Sun, 2 Jun 2024 05:57:02 +0200 Subject: [PATCH 20/49] rename artemis_url to lms_url --- .../assessment_module_manager/authenticate.py | 12 ++--- .../module/request_to_module.py | 4 +- athena/athena/authenticate.py | 6 +-- athena/athena/contextvars.py | 6 +-- athena/athena/models/model.py | 4 +- athena/athena/storage/exercise_storage.py | 28 +++++------ athena/athena/storage/feedback_storage.py | 46 +++++++++---------- athena/athena/storage/submission_storage.py | 36 +++++++-------- 8 files changed, 71 insertions(+), 71 deletions(-) diff --git a/assessment_module_manager/assessment_module_manager/authenticate.py b/assessment_module_manager/assessment_module_manager/authenticate.py index 1b3b6457f..cae887c23 100644 --- a/assessment_module_manager/assessment_module_manager/authenticate.py +++ b/assessment_module_manager/assessment_module_manager/authenticate.py @@ -12,13 +12,13 @@ api_key_artemis_url_header = APIKeyHeader(name='X-Server-URL', auto_error=False) -def verify_artemis_athena_key(artemis_url: str, secret: str): - if artemis_url is None: +def verify_artemis_athena_key(lms_url: str, secret: str): + if lms_url is None: raise HTTPException(status_code=401, detail="Invalid Artemis Server Url.") # cannot proceed even for local development # database entries cannot be set uniquely - if artemis_url not in env.DEPLOYMENT_SECRETS or secret != env.DEPLOYMENT_SECRETS[artemis_url]: + if lms_url not in env.DEPLOYMENT_SECRETS or secret != env.DEPLOYMENT_SECRETS[lms_url]: if env.PRODUCTION: raise HTTPException(status_code=401, detail="Invalid API secret.") logger.warning("DEBUG MODE: Ignoring invalid Artemis Deployment secret.") @@ -36,9 +36,9 @@ def endpoint(): @wraps(func) async def wrapper(*args, secret: str = Depends(api_key_auth_header), - artemis_url: str = Depends(api_key_artemis_url_header), + lms_url: str = Depends(api_key_artemis_url_header), **kwargs): - verify_artemis_athena_key(artemis_url, secret) # this happens in scope of the ASM Module + verify_artemis_athena_key(lms_url, secret) # this happens in scope of the ASM Module if inspect.iscoroutinefunction(func): return await func(*args, **kwargs) return func(*args, **kwargs) @@ -48,7 +48,7 @@ async def wrapper(*args, secret: str = Depends(api_key_auth_header), params = list(sig.parameters.values()) params.append( inspect.Parameter('secret', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_auth_header))) - params.append(inspect.Parameter('artemis_url', inspect.Parameter.POSITIONAL_OR_KEYWORD, + params.append(inspect.Parameter('lms_url', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_artemis_url_header))) new_sig = sig.replace(parameters=params) wrapper.__signature__ = new_sig # type: ignore # https://github.com/python/mypy/issues/12472 diff --git a/assessment_module_manager/assessment_module_manager/module/request_to_module.py b/assessment_module_manager/assessment_module_manager/module/request_to_module.py index 22a509c9d..a0d1a933a 100644 --- a/assessment_module_manager/assessment_module_manager/module/request_to_module.py +++ b/assessment_module_manager/assessment_module_manager/module/request_to_module.py @@ -33,7 +33,7 @@ async def find_module_by_name(module_name: str) -> Optional[Module]: return None -async def request_to_module(module: Module, headers: dict, path: str, artemis_url: str, data: Optional[dict], method: str) -> ModuleResponse: +async def request_to_module(module: Module, headers: dict, path: str, lms_url: str, data: Optional[dict], method: str) -> ModuleResponse: """ Helper function to send a request to a module. It raises appropriate FastAPI HTTPException if the request fails. @@ -46,7 +46,7 @@ async def request_to_module(module: Module, headers: dict, path: str, artemis_ur # We need the Athena secret with the LMS to access repositories. # In order to only have to configure it once for the whole of Athena, # we pass it to the module from here. - headers['X-Repository-Authorization-Secret'] = env.DEPLOYMENT_SECRETS.get(artemis_url, "") + headers['X-Repository-Authorization-Secret'] = env.DEPLOYMENT_SECRETS.get(lms_url, "") # for repository access # should be the same as the Artemis key diff --git a/athena/athena/authenticate.py b/athena/athena/authenticate.py index 6f74af10b..ed96fc5cf 100644 --- a/athena/athena/authenticate.py +++ b/athena/athena/authenticate.py @@ -33,10 +33,10 @@ def endpoint(): @wraps(func) async def wrapper(*args, secret: str = Depends(api_key_auth_header), - artemis_url: str = Depends(api_key_artemis_url_header), + lms_url: str = Depends(api_key_artemis_url_header), **kwargs): verify_inter_module_secret_key(secret) # this happens after the ASM Module reissued the request - set_artemis_url_context_var(artemis_url) + set_artemis_url_context_var(lms_url) if inspect.iscoroutinefunction(func): return await func(*args, **kwargs) return func(*args, **kwargs) @@ -46,7 +46,7 @@ async def wrapper(*args, secret: str = Depends(api_key_auth_header), params = list(sig.parameters.values()) params.append( inspect.Parameter('secret', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_auth_header))) - params.append(inspect.Parameter('artemis_url', inspect.Parameter.POSITIONAL_OR_KEYWORD, + params.append(inspect.Parameter('lms_url', inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Depends(api_key_artemis_url_header))) new_sig = sig.replace(parameters=params) wrapper.__signature__ = new_sig # type: ignore # https://github.com/python/mypy/issues/12472 diff --git a/athena/athena/contextvars.py b/athena/athena/contextvars.py index fea5650cc..5dbd0d210 100644 --- a/athena/athena/contextvars.py +++ b/athena/athena/contextvars.py @@ -1,12 +1,12 @@ import contextvars -artemis_url_context_var: contextvars.ContextVar = contextvars.ContextVar('artemis_url') +artemis_url_context_var: contextvars.ContextVar = contextvars.ContextVar('lms_url') repository_authorization_secret_context_var: contextvars.ContextVar = contextvars.ContextVar( 'repository_authorization_secret') -def set_artemis_url_context_var(artemis_url: str): - artemis_url_context_var.set(artemis_url) +def set_artemis_url_context_var(lms_url: str): + artemis_url_context_var.set(lms_url) def get_artemis_url(): diff --git a/athena/athena/models/model.py b/athena/athena/models/model.py index 16fb8580e..6ce8d1293 100644 --- a/athena/athena/models/model.py +++ b/athena/athena/models/model.py @@ -6,10 +6,10 @@ class Model: - artemis_url = Column(String, index=True, nullable=False) + lms_url = Column(String, index=True, nullable=False) __table_args__ = ( - UniqueConstraint('id', 'artemis_url'), + UniqueConstraint('id', 'lms_url'), ) @classmethod diff --git a/athena/athena/storage/exercise_storage.py b/athena/athena/storage/exercise_storage.py index dce29ecf7..0df953716 100644 --- a/athena/athena/storage/exercise_storage.py +++ b/athena/athena/storage/exercise_storage.py @@ -5,51 +5,51 @@ from athena.schemas import Exercise -def get_stored_exercises(exercise_cls: Type[Exercise], artemis_url: Optional[str] = None, only_ids: Optional[List[int]] = None) -> \ +def get_stored_exercises(exercise_cls: Type[Exercise], lms_url: Optional[str] = None, only_ids: Optional[List[int]] = None) -> \ Iterable[Exercise]: """ Returns a list of exercises for the given exercise type and exercise ids. If only_ids is None, returns all exercises for the given exercise type. """ - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_exercise_cls = exercise_cls.get_model_class() with get_db() as db: query = db.query(db_exercise_cls) if only_ids is not None: query = query.filter(db_exercise_cls.id.in_(only_ids)) # type: ignore - query = query.filter(db_exercise_cls.artemis_url == artemis_url) # type: ignore + query = query.filter(db_exercise_cls.lms_url == lms_url) # type: ignore return (e.to_schema() for e in query.all()) -def get_stored_exercise_meta(exercise: Exercise, artemis_url: Optional[str] = None, ) -> Optional[dict]: +def get_stored_exercise_meta(exercise: Exercise, lms_url: Optional[str] = None, ) -> Optional[dict]: """Returns the stored metadata associated with the exercise.""" - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_exercise_cls: Type[Exercise] = exercise.__class__.get_model_class() with get_db() as db: return db.query(db_exercise_cls.meta).filter_by(id=exercise.id, - artemis_url=artemis_url).scalar() # type: ignore + lms_url=lms_url).scalar() # type: ignore -def store_exercises(exercises: List[Exercise], artemis_url: Optional[str] = None): +def store_exercises(exercises: List[Exercise], lms_url: Optional[str] = None): """Stores the given exercises, all at once.""" - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() with get_db() as db: for e in exercises: exercise_model = e.to_model() - exercise_model.artemis_url = artemis_url + exercise_model.lms_url = lms_url db.merge(exercise_model) db.commit() -def store_exercise(exercise: Exercise, artemis_url: Optional[str] = None): +def store_exercise(exercise: Exercise, lms_url: Optional[str] = None): """Stores the given exercise.""" - store_exercises([exercise], artemis_url) + store_exercises([exercise], lms_url) diff --git a/athena/athena/storage/feedback_storage.py b/athena/athena/storage/feedback_storage.py index 3bd2ddd1e..f4e45f333 100644 --- a/athena/athena/storage/feedback_storage.py +++ b/athena/athena/storage/feedback_storage.py @@ -6,49 +6,49 @@ def get_stored_feedback( - feedback_cls: Type[Feedback], exercise_id: int, submission_id: Union[int, None], artemis_url: Optional[str] = None + feedback_cls: Type[Feedback], exercise_id: int, submission_id: Union[int, None], lms_url: Optional[str] = None ) -> Iterable[Feedback]: """ Returns a list of feedbacks for the given exercise in the given submission. If submission_id is None, returns all feedbacks for the given exercise. """ - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_feedback_cls = feedback_cls.get_model_class() with get_db() as db: - query = db.query(db_feedback_cls).filter_by(exercise_id=exercise_id, is_suggestion=0, artemis_url=artemis_url) + query = db.query(db_feedback_cls).filter_by(exercise_id=exercise_id, is_suggestion=0, lms_url=lms_url) if submission_id is not None: query = query.filter_by(submission_id=submission_id) return (f.to_schema() for f in query.all()) -def get_stored_feedback_meta(feedback: Feedback, artemis_url: Optional[str] = None) -> Optional[dict]: +def get_stored_feedback_meta(feedback: Feedback, lms_url: Optional[str] = None) -> Optional[dict]: """Returns the stored metadata associated with the feedback.""" - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_feedback_cls = feedback.__class__.get_model_class() with get_db() as db: return db.query(db_feedback_cls.meta).filter_by(id=feedback.id, # type: ignore - artemis_url=artemis_url).scalar() + lms_url=lms_url).scalar() -def store_feedback(feedback: Feedback, is_lms_id=False, artemis_url: Optional[str] = None) -> Feedback: +def store_feedback(feedback: Feedback, is_lms_id=False, lms_url: Optional[str] = None) -> Feedback: """Stores the given LMS feedback. Args: feedback (Feedback): The feedback to store. is_lms_id (bool, optional): Whether the feedback's ID is an LMS ID. Defaults to False. - artemis_url (str, optional): The URL of the Artemis instance that issued the query + lms_url (str, optional): The URL of the Artemis instance that issued the query Returns: Feedback: The stored feedback with its internal ID assigned. """ - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_feedback_cls = feedback.__class__.get_model_class() with get_db() as db: @@ -56,7 +56,7 @@ def store_feedback(feedback: Feedback, is_lms_id=False, artemis_url: Optional[st if is_lms_id: lms_id = feedback.id internal_id = db.query(db_feedback_cls.id).filter_by(lms_id=lms_id, # type: ignore - artemis_url=artemis_url).scalar() + lms_url=lms_url).scalar() feedback.id = internal_id stored_feedback_model = db.merge(feedback.to_model(lms_id=lms_id)) @@ -65,37 +65,37 @@ def store_feedback(feedback: Feedback, is_lms_id=False, artemis_url: Optional[st def get_stored_feedback_suggestions( - feedback_cls: Type[Feedback], exercise_id: int, submission_id: int, artemis_url: Optional[str] = None + feedback_cls: Type[Feedback], exercise_id: int, submission_id: int, lms_url: Optional[str] = None ) -> Iterable[Feedback]: """Returns a list of feedback suggestions for the given exercise in the given submission.""" - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_feedback_cls = feedback_cls.get_model_class() with get_db() as db: query = db.query(db_feedback_cls).filter_by(exercise_id=exercise_id, is_suggestion=True, - artemis_url=artemis_url) + lms_url=lms_url) if submission_id is not None: query = query.filter_by(submission_id=submission_id) return (f.to_schema() for f in query.all()) -def store_feedback_suggestions(feedbacks: List[Feedback], artemis_url: Optional[str] = None) -> List[Feedback]: +def store_feedback_suggestions(feedbacks: List[Feedback], lms_url: Optional[str] = None) -> List[Feedback]: """Stores the given feedbacks as a suggestions. Returns: List[Feedback]: The stored feedback suggestions with their internal IDs assigned. """ - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() stored_feedbacks: List[Feedback] = [] with get_db() as db: for feedback in feedbacks: feedback_model = feedback.to_model(is_suggestion=True) - feedback_model.artemis_url = artemis_url + feedback_model.lms_url = lms_url feedback_model = db.merge(feedback_model) db.flush() # Ensure the ID is generated now stored_feedbacks.append(feedback_model.to_schema()) @@ -103,6 +103,6 @@ def store_feedback_suggestions(feedbacks: List[Feedback], artemis_url: Optional[ return stored_feedbacks -def store_feedback_suggestion(feedback: Feedback, artemis_url: Optional[str] = None): +def store_feedback_suggestion(feedback: Feedback, lms_url: Optional[str] = None): """Stores the given feedback as a suggestion.""" - store_feedback_suggestions([feedback], artemis_url) + store_feedback_suggestions([feedback], lms_url) diff --git a/athena/athena/storage/submission_storage.py b/athena/athena/storage/submission_storage.py index a6667d7a1..e14c0aba9 100644 --- a/athena/athena/storage/submission_storage.py +++ b/athena/athena/storage/submission_storage.py @@ -6,65 +6,65 @@ def count_stored_submissions( - submission_cls: Type[Submission], exercise_id: int, artemis_url: Optional[str] = None + submission_cls: Type[Submission], exercise_id: int, lms_url: Optional[str] = None ) -> int: """Returns the number of submissions for the given exercise.""" - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_submission_cls = submission_cls.get_model_class() with get_db() as db: return db.query(db_submission_cls).filter_by(exercise_id=exercise_id, - artemis_url=artemis_url).count() # type: ignore + lms_url=lms_url).count() # type: ignore def get_stored_submissions( submission_cls: Type[Submission], exercise_id: int, only_ids: Union[List[int], None] = None, - artemis_url: Optional[str] = None + lms_url: Optional[str] = None ) -> Iterable[Submission]: """ Returns a list of submissions for the given exercise and submission ids. If only_ids is None, returns all submissions for the given exercise. """ - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_submission_cls = submission_cls.get_model_class() with get_db() as db: - query = db.query(db_submission_cls).filter_by(exercise_id=exercise_id, artemis_url=artemis_url) + query = db.query(db_submission_cls).filter_by(exercise_id=exercise_id, lms_url=lms_url) if only_ids is not None: query = query.filter(db_submission_cls.id.in_(only_ids)) # type: ignore return (s.to_schema() for s in query.all()) -def get_stored_submission_meta(submission: Submission, artemis_url: Optional[str] = None) -> Optional[dict]: +def get_stored_submission_meta(submission: Submission, lms_url: Optional[str] = None) -> Optional[dict]: """Returns the stored metadata associated with the submission.""" - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() db_submission_cls = submission.__class__.get_model_class() with get_db() as db: return db.query(db_submission_cls.meta).filter_by(id=submission.id, # type: ignore - artemis_url=artemis_url).scalar() + lms_url=lms_url).scalar() -def store_submissions(submissions: List[Submission], artemis_url: Optional[str] = None): +def store_submissions(submissions: List[Submission], lms_url: Optional[str] = None): """Stores the given submissions, all at once.""" - if artemis_url is None: - artemis_url = get_artemis_url() + if lms_url is None: + lms_url = get_artemis_url() with get_db() as db: for s in submissions: submission_model = s.to_model() - submission_model.artemis_url = artemis_url + submission_model.lms_url = lms_url db.merge(submission_model) db.commit() -def store_submission(submission: Submission, artemis_url: Optional[str] = None): +def store_submission(submission: Submission, lms_url: Optional[str] = None): """Stores the given submission.""" - store_submissions([submission], artemis_url) + store_submissions([submission], lms_url) From e0be30989ed344e4226ca280e36affeb63091c95 Mon Sep 17 00:00:00 2001 From: Dmytro Polityka Date: Mon, 3 Jun 2024 01:14:19 +0200 Subject: [PATCH 21/49] make playground work for multi-instance setup; add non-graded feedback suggestions --- .../deployments.docker.ini | 5 +- assessment_module_manager/deployments.ini | 5 +- .../generate_summary_by_file.py | 2 +- .../split_problem_statement_by_file.py | 2 +- playground/data/example/exercise-1.json | 12 +- .../4_link_programming_repositories.mjs | 16 +- .../src/components/base_info_header.tsx | 81 ++++++----- .../details/exercise_detail/programming.tsx | 6 +- .../details/submission_detail/programming.tsx | 2 +- .../selectors/submission_select.tsx | 2 +- .../view_mode/module_requests/index.tsx | 6 +- ...> request_graded_feedback_suggestions.tsx} | 15 +- ...equest_non_graded_feedback_suggestions.tsx | 137 ++++++++++++++++++ playground/src/helpers/athena_fetcher.ts | 3 +- .../athena/request_feedback_suggestions.ts | 6 +- playground/src/hooks/athena_fetcher.ts | 3 +- playground/src/hooks/base_info_context.tsx | 16 +- playground/src/model/exercise.ts | 6 +- playground/src/model/submission.ts | 2 +- playground/src/pages/api/athena_request.ts | 2 + 20 files changed, 249 insertions(+), 80 deletions(-) rename playground/src/components/view_mode/module_requests/{request_feedback_suggestions.tsx => request_graded_feedback_suggestions.tsx} (88%) create mode 100644 playground/src/components/view_mode/module_requests/request_non_graded_feedback_suggestions.tsx diff --git a/assessment_module_manager/deployments.docker.ini b/assessment_module_manager/deployments.docker.ini index 64f60b3ea..0150d1612 100644 --- a/assessment_module_manager/deployments.docker.ini +++ b/assessment_module_manager/deployments.docker.ini @@ -1,2 +1,5 @@ [schwind] -url = https://ma-schwind.ase.cit.tum.de \ No newline at end of file +url = https://ma-schwind.ase.cit.tum.de + +[playground] +url = https://athenetest1-03.ase.cit.tum.de \ No newline at end of file diff --git a/assessment_module_manager/deployments.ini b/assessment_module_manager/deployments.ini index 1c55f4614..a74bcbeea 100644 --- a/assessment_module_manager/deployments.ini +++ b/assessment_module_manager/deployments.ini @@ -1,4 +1,7 @@ # for local development only [local] -url = http://localhost:8080 \ No newline at end of file +url = http://localhost:8080 + +[playground] +url = http://localhost:3000 \ No newline at end of file diff --git a/module_programming_llm/module_programming_llm/generate_summary_by_file.py b/module_programming_llm/module_programming_llm/generate_summary_by_file.py index a42315b34..144d750bf 100644 --- a/module_programming_llm/module_programming_llm/generate_summary_by_file.py +++ b/module_programming_llm/module_programming_llm/generate_summary_by_file.py @@ -91,7 +91,7 @@ async def generate_summary_by_file( model=model, system_message=config.generate_file_summary_prompt.system_message, human_message=config.generate_file_summary_prompt.human_message, - pydantic_object=FileDescription, + pydantic_object=SolutionSummary, ) prompt_inputs = [] diff --git a/module_programming_llm/module_programming_llm/split_problem_statement_by_file.py b/module_programming_llm/module_programming_llm/split_problem_statement_by_file.py index 86f841ee1..46cd64bbb 100644 --- a/module_programming_llm/module_programming_llm/split_problem_statement_by_file.py +++ b/module_programming_llm/module_programming_llm/split_problem_statement_by_file.py @@ -80,7 +80,7 @@ async def split_problem_statement_by_file( "changed_files_from_template_to_submission": ", ".join(changed_files_from_template_to_submission) } - if "changed_files_from_template_to_solution" in prompt.input_variables: + if "changed_files_from_template_to_solution" in chat_prompt.input_variables: solution_repo = exercise.get_solution_repository() changed_files_from_template_to_solution = get_diff( src_repo=template_repo, diff --git a/playground/data/example/exercise-1.json b/playground/data/example/exercise-1.json index d7ecfa463..fcd9bd867 100644 --- a/playground/data/example/exercise-1.json +++ b/playground/data/example/exercise-1.json @@ -8,15 +8,15 @@ "problem_statement": "-> file:problem-statement.md", "grading_instructions": "-> file:grading-instructions.md", "programming_language": "java", - "solution_repository_url": "{{exerciseDataUrl}}/solution.zip", - "template_repository_url": "{{exerciseDataUrl}}/template.zip", - "tests_repository_url": "{{exerciseDataUrl}}/tests.zip", + "solution_repository_uri": "{{exerciseDataUrl}}/solution.zip", + "template_repository_uri": "{{exerciseDataUrl}}/template.zip", + "tests_repository_uri": "{{exerciseDataUrl}}/tests.zip", "meta": {}, "submissions": [ { "id": 101, - "repository_url": "{{exerciseDataUrl}}/submissions/101.zip", + "repository_uri": "{{exerciseDataUrl}}/submissions/101.zip", "meta": {}, "feedbacks": [ { @@ -43,12 +43,12 @@ }, { "id": 102, - "repository_url": "{{exerciseDataUrl}}/submissions/102.zip", + "repository_uri": "{{exerciseDataUrl}}/submissions/102.zip", "meta": {} }, { "id": 103, - "repository_url": "{{exerciseDataUrl}}/submissions/103.zip", + "repository_uri": "{{exerciseDataUrl}}/submissions/103.zip", "meta": {}, "feedbacks": [ { diff --git a/playground/scripts/artemis/4_link_programming_repositories.mjs b/playground/scripts/artemis/4_link_programming_repositories.mjs index 20583f5e5..a73b0f998 100644 --- a/playground/scripts/artemis/4_link_programming_repositories.mjs +++ b/playground/scripts/artemis/4_link_programming_repositories.mjs @@ -32,23 +32,23 @@ for (let exercise of exercises) { if (!fs.existsSync(path.join(exercisePath, "solution"))) { console.log(`Exercise ${exercise.id} has no solution at ${exercisePath}/solution`); - exercise.solution_repository_url = null; + exercise.solution_repository_uri = null; } else { - exercise.solution_repository_url = `{{exerciseDataUrl}}/solution.zip`; + exercise.solution_repository_uri = `{{exerciseDataUrl}}/solution.zip`; } if (!fs.existsSync(path.join(exercisePath, "template"))) { console.log(`Exercise ${exercise.id} has no template at ${exercisePath}/template`); - exercise.template_repository_url = null; + exercise.template_repository_uri = null; } else { - exercise.template_repository_url = `{{exerciseDataUrl}}/template.zip`; + exercise.template_repository_uri = `{{exerciseDataUrl}}/template.zip`; } if (!fs.existsSync(path.join(exercisePath, "tests"))) { console.log(`Exercise ${exercise.id} has no tests at ${exercisePath}/tests`); - exercise.tests_repository_url = null; + exercise.tests_repository_uri = null; } else { - exercise.tests_repository_url = `{{exerciseDataUrl}}/tests.zip`; + exercise.tests_repository_uri = `{{exerciseDataUrl}}/tests.zip`; } const submissionsPath = path.join(exercisePath, "submissions"); @@ -56,9 +56,9 @@ for (let exercise of exercises) { const submissionPath = path.join(submissionsPath, `${submission.id}`); if (!fs.existsSync(submissionPath)) { console.log(`Submission ${submission.id} has no directory at ${submissionPath}`); - submission.repository_url = null; + submission.repository_uri = null; } else { - submission.repository_url = `{{exerciseDataUrl}}/submissions/${submission.id}.zip`; + submission.repository_uri = `{{exerciseDataUrl}}/submissions/${submission.id}.zip`; } return submission; }); diff --git a/playground/src/components/base_info_header.tsx b/playground/src/components/base_info_header.tsx index 2343e469b..03ca09250 100644 --- a/playground/src/components/base_info_header.tsx +++ b/playground/src/components/base_info_header.tsx @@ -5,43 +5,54 @@ import DataModeSelect from "@/components/selectors/data_mode_select"; import ViewModeSelect from "@/components/selectors/view_mode_select"; export default function BaseInfoHeader() { - const { dataMode, viewMode, athenaUrl, athenaSecret } = useBaseInfo(); + const { dataMode, viewMode, athenaUrl, athenaSecret, lmsUrl } = useBaseInfo(); const dispatch = useBaseInfoDispatch(); return ( -
- - - -
- dispatch({ type: "SET_DATA_MODE", payload: dataMode })} - /> -
- dispatch({ type: "SET_VIEW_MODE", payload: viewMode })} - /> -
+
+ + + + +
+ dispatch({type: "SET_DATA_MODE", payload: dataMode})} + /> +
+ dispatch({type: "SET_VIEW_MODE", payload: viewMode})} + /> +
); } diff --git a/playground/src/components/details/exercise_detail/programming.tsx b/playground/src/components/details/exercise_detail/programming.tsx index 01d9a946f..e88bf1f33 100644 --- a/playground/src/components/details/exercise_detail/programming.tsx +++ b/playground/src/components/details/exercise_detail/programming.tsx @@ -7,13 +7,13 @@ export default function ProgrammingExerciseDetail({ exercise, openedInitially }: return ( <> - + - + - + ); diff --git a/playground/src/components/details/submission_detail/programming.tsx b/playground/src/components/details/submission_detail/programming.tsx index f63d223ae..4f99c5d9a 100644 --- a/playground/src/components/details/submission_detail/programming.tsx +++ b/playground/src/components/details/submission_detail/programming.tsx @@ -30,7 +30,7 @@ export default function ProgrammingSubmissionDetail({ createNewFeedback(submission)} diff --git a/playground/src/components/selectors/submission_select.tsx b/playground/src/components/selectors/submission_select.tsx index 4d38fa5dd..915bac023 100644 --- a/playground/src/components/selectors/submission_select.tsx +++ b/playground/src/components/selectors/submission_select.tsx @@ -57,7 +57,7 @@ export default function SubmissionSelect({ {data?.map((sub: Submission) => { const contentPreview = (sub as TextSubmission)?.text || - (sub as ProgrammingSubmission)?.repository_url || + (sub as ProgrammingSubmission)?.repository_uri || "?"; return (