Compare commits
68 commits
main
...
feat/auth0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c06fd6ffd2 | ||
|
|
450d9ada33 | ||
|
|
748671484e | ||
|
|
bcb5b12964 | ||
|
|
7f3aebd06d | ||
|
|
8e85cd2ae3 | ||
|
|
e14609bc4f | ||
|
|
f0022e2f07 | ||
|
|
939aaf1b5e | ||
|
|
a500aeba80 | ||
|
|
880f7d4c5d | ||
|
|
55943630bd | ||
|
|
e644c3b86d | ||
|
|
29ec092520 | ||
|
|
f0f3a5de5f | ||
|
|
7c3d7eb718 | ||
|
|
b057cf27f5 | ||
|
|
9e473996d3 | ||
|
|
ad462d8510 | ||
|
|
569ffdead0 | ||
|
|
7053ce7c84 | ||
|
|
5582a4cd69 | ||
|
|
72efd83061 | ||
|
|
046e8dcdc6 | ||
|
|
135c2c7520 | ||
|
|
1d2fd2f2ec | ||
|
|
34ee9f577d | ||
|
|
a89d5570ff | ||
|
|
f8110c4548 | ||
|
|
450320ba2c | ||
|
|
c68175d3f5 | ||
|
|
a1bf8416bd | ||
|
|
c803042280 | ||
|
|
c14f1a5fb0 | ||
|
|
d47d410499 | ||
|
|
9e09d26501 | ||
|
|
f8f400dbeb | ||
|
|
b11236f592 | ||
|
|
c383253195 | ||
|
|
9aa8e543cb | ||
|
|
6466f66a76 | ||
|
|
2383843ec7 | ||
|
|
c908aefd80 | ||
|
|
4ca3baa383 | ||
|
|
70d49745d9 | ||
|
|
d7a8b29147 | ||
|
|
893fdd1588 | ||
|
|
232ac4e271 | ||
|
|
00948ec8db | ||
|
|
1361203ead | ||
|
|
472143df03 | ||
|
|
cb7a8951ff | ||
|
|
7865b4ce3e | ||
|
|
2871d68673 | ||
|
|
bc3d35d51e | ||
|
|
5cb1b53ddd | ||
|
|
2d0d7fa71c | ||
|
|
4a58913e55 | ||
|
|
42be438ab6 | ||
|
|
70e307a905 | ||
|
|
ddfa506cf8 | ||
|
|
be5e5078b3 | ||
|
|
7e3d593684 | ||
|
|
e3dbc186fd | ||
|
|
6f78462f3c | ||
|
|
4ddfdc13c8 | ||
|
|
cdaf4afba8 | ||
|
|
7e8f5473a7 |
99 changed files with 2291 additions and 538 deletions
|
|
@ -69,3 +69,11 @@ LITELLM_LOG="ERROR"
|
|||
# Set this environment variable to disable sending telemetry data
|
||||
# TELEMETRY_DISABLED=1
|
||||
|
||||
# Set this variable to True to enforce usage of backend access control for Cognee
|
||||
# Note: This is only currently supported by the following databases:
|
||||
# Relational: SQLite, Postgres
|
||||
# Vector: LanceDB
|
||||
# Graph: KuzuDB
|
||||
#
|
||||
# It enforces LanceDB and KuzuDB use and uses them to create databases per Cognee user + dataset
|
||||
ENABLE_BACKEND_ACCESS_CONTROL=False
|
||||
|
|
|
|||
31
.github/workflows/e2e_tests.yml
vendored
31
.github/workflows/e2e_tests.yml
vendored
|
|
@ -215,3 +215,34 @@ jobs:
|
|||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
run: poetry run python ./cognee/tests/test_s3.py
|
||||
|
||||
test-parallel-databases:
|
||||
name: Test using different async databases in parallel in Cognee
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cognee Setup
|
||||
uses: ./.github/actions/cognee_setup
|
||||
with:
|
||||
python-version: '3.11.x'
|
||||
|
||||
- name: Install specific graph db dependency
|
||||
run: |
|
||||
poetry install -E kuzu
|
||||
|
||||
- name: Run parallel databases test
|
||||
env:
|
||||
ENV: 'dev'
|
||||
LLM_MODEL: ${{ secrets.LLM_MODEL }}
|
||||
LLM_ENDPOINT: ${{ secrets.LLM_ENDPOINT }}
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
LLM_API_VERSION: ${{ secrets.LLM_API_VERSION }}
|
||||
EMBEDDING_MODEL: ${{ secrets.EMBEDDING_MODEL }}
|
||||
EMBEDDING_ENDPOINT: ${{ secrets.EMBEDDING_ENDPOINT }}
|
||||
EMBEDDING_API_KEY: ${{ secrets.EMBEDDING_API_KEY }}
|
||||
EMBEDDING_API_VERSION: ${{ secrets.EMBEDDING_API_VERSION }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
run: poetry run python ./cognee/tests/test_parallel_databases.py
|
||||
|
|
|
|||
688
cognee-frontend/package-lock.json
generated
688
cognee-frontend/package-lock.json
generated
|
|
@ -8,11 +8,12 @@
|
|||
"name": "cognee-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@auth0/nextjs-auth0": "^4.6.0",
|
||||
"classnames": "^2.5.1",
|
||||
"next": "14.2.3",
|
||||
"next": "15.2.3",
|
||||
"ohmy-ui": "^0.0.6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -64,6 +65,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@auth0/nextjs-auth0": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@auth0/nextjs-auth0/-/nextjs-auth0-4.6.0.tgz",
|
||||
"integrity": "sha512-HK+fcUW6P8/qUDQfOfntftMg6yzeZLtyfTxL/lyeOub1o/xTL9SZ2fF39nH0H6w1loB5SCAbyN1vD8xxBwINqQ==",
|
||||
"dependencies": {
|
||||
"@edge-runtime/cookies": "^5.0.1",
|
||||
"@panva/hkdf": "^1.2.1",
|
||||
"jose": "^5.9.6",
|
||||
"oauth4webapi": "^3.1.2",
|
||||
"swr": "^2.2.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^14.2.25 || ^15.2.3",
|
||||
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.24.5",
|
||||
"dev": true,
|
||||
|
|
@ -75,6 +93,23 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@edge-runtime/cookies": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@edge-runtime/cookies/-/cookies-5.0.2.tgz",
|
||||
"integrity": "sha512-Sd8LcWpZk/SWEeKGE8LT6gMm5MGfX/wm+GPnh1eBEtCpya3vYqn37wYknwAHw92ONoyyREl1hJwxV/Qx2DWNOg==",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
|
||||
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
"dev": true,
|
||||
|
|
@ -157,6 +192,348 @@
|
|||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
|
||||
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
|
||||
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
|
||||
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
|
||||
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
|
||||
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
|
||||
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"dev": true,
|
||||
|
|
@ -199,8 +576,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "14.2.3",
|
||||
"license": "MIT"
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.3.tgz",
|
||||
"integrity": "sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw=="
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
"version": "14.2.3",
|
||||
|
|
@ -254,11 +632,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "14.2.3",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.3.tgz",
|
||||
"integrity": "sha512-uaBhA8aLbXLqwjnsHSkxs353WrRgQgiFjduDpc7YXEU0B54IKx3vU+cxQlYwPCyC8uYEEX7THhtQQsfHnvv8dw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
|
|
@ -268,9 +647,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz",
|
||||
"integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.3.tgz",
|
||||
"integrity": "sha512-pVwKvJ4Zk7h+4hwhqOUuMx7Ib02u3gDX3HXPKIShBi9JlYllI0nU6TWLbPT94dt7FSi6mSBhfc2JrHViwqbOdw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -283,9 +662,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz",
|
||||
"integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.3.tgz",
|
||||
"integrity": "sha512-50ibWdn2RuFFkOEUmo9NCcQbbV9ViQOrUfG48zHBCONciHjaUKtHcYFiCwBVuzD08fzvzkWuuZkd4AqbvKO7UQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -298,9 +677,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz",
|
||||
"integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.3.tgz",
|
||||
"integrity": "sha512-2gAPA7P652D3HzR4cLyAuVYwYqjG0mt/3pHSWTCyKZq/N/dJcUAEoNQMyUmwTZWCJRKofB+JPuDVP2aD8w2J6Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -313,9 +692,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz",
|
||||
"integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.3.tgz",
|
||||
"integrity": "sha512-ODSKvrdMgAJOVU4qElflYy1KSZRM3M45JVbeZu42TINCMG3anp7YCBn80RkISV6bhzKwcUqLBAmOiWkaGtBA9w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -328,9 +707,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz",
|
||||
"integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.3.tgz",
|
||||
"integrity": "sha512-ZR9kLwCWrlYxwEoytqPi1jhPd1TlsSJWAc+H/CJHmHkf2nD92MQpSRIURR1iNgA/kuFSdxB8xIPt4p/T78kwsg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -343,9 +722,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz",
|
||||
"integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.3.tgz",
|
||||
"integrity": "sha512-+G2FrDcfm2YDbhDiObDU/qPriWeiz/9cRR0yMWJeTLGGX6/x8oryO3tt7HhodA1vZ8r2ddJPCjtLcpaVl7TE2Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -357,25 +736,10 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-ia32-msvc": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz",
|
||||
"integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz",
|
||||
"integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.3.tgz",
|
||||
"integrity": "sha512-gHYS9tc+G2W0ZC8rBL+H6RdtXIyk40uLiaos0yj5US85FNhbFEndMA2nW3z47nzOWiSvXTZ5kBClc3rD0zJg0w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -419,6 +783,14 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@panva/hkdf": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
||||
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"dev": true,
|
||||
|
|
@ -435,14 +807,15 @@
|
|||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"license": "Apache-2.0"
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.5",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"tslib": "^2.4.0"
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
|
|
@ -971,11 +1344,25 @@
|
|||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"license": "MIT"
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
|
|
@ -986,9 +1373,19 @@
|
|||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"dev": true,
|
||||
|
|
@ -1120,12 +1517,20 @@
|
|||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"dev": true,
|
||||
|
|
@ -2020,6 +2425,7 @@
|
|||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/graphemer": {
|
||||
|
|
@ -2174,6 +2580,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/is-async-function": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
|
|
@ -2526,8 +2938,17 @@
|
|||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
|
||||
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
|
|
@ -2638,6 +3059,7 @@
|
|||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
|
|
@ -2728,39 +3150,41 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "14.2.3",
|
||||
"license": "MIT",
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.2.3.tgz",
|
||||
"integrity": "sha512-x6eDkZxk2rPpu46E1ZVUWIBhYCLszmUY6fvHBFcbzJ9dD+qRX6vcHusaqqDlnY+VngKzKbAiG2iRCkPbmi8f7w==",
|
||||
"dependencies": {
|
||||
"@next/env": "14.2.3",
|
||||
"@swc/helpers": "0.5.5",
|
||||
"@next/env": "15.2.3",
|
||||
"@swc/counter": "0.1.3",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"busboy": "1.6.0",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"postcss": "8.4.31",
|
||||
"styled-jsx": "5.1.1"
|
||||
"styled-jsx": "5.1.6"
|
||||
},
|
||||
"bin": {
|
||||
"next": "dist/bin/next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "14.2.3",
|
||||
"@next/swc-darwin-x64": "14.2.3",
|
||||
"@next/swc-linux-arm64-gnu": "14.2.3",
|
||||
"@next/swc-linux-arm64-musl": "14.2.3",
|
||||
"@next/swc-linux-x64-gnu": "14.2.3",
|
||||
"@next/swc-linux-x64-musl": "14.2.3",
|
||||
"@next/swc-win32-arm64-msvc": "14.2.3",
|
||||
"@next/swc-win32-ia32-msvc": "14.2.3",
|
||||
"@next/swc-win32-x64-msvc": "14.2.3"
|
||||
"@next/swc-darwin-arm64": "15.2.3",
|
||||
"@next/swc-darwin-x64": "15.2.3",
|
||||
"@next/swc-linux-arm64-gnu": "15.2.3",
|
||||
"@next/swc-linux-arm64-musl": "15.2.3",
|
||||
"@next/swc-linux-x64-gnu": "15.2.3",
|
||||
"@next/swc-linux-x64-musl": "15.2.3",
|
||||
"@next/swc-win32-arm64-msvc": "15.2.3",
|
||||
"@next/swc-win32-x64-msvc": "15.2.3",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "^1.1.0",
|
||||
"@playwright/test": "^1.41.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"babel-plugin-react-compiler": "*",
|
||||
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"sass": "^1.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
|
|
@ -2770,11 +3194,22 @@
|
|||
"@playwright/test": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-react-compiler": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/oauth4webapi": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.5.1.tgz",
|
||||
"integrity": "sha512-txg/jZQwcbaF7PMJgY7aoxc9QuCxHVFMiEkDIJ60DwDz3PbtXPQnrzo+3X4IRYGChIwWLabRBRpf1k9hO9+xrQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"dev": true,
|
||||
|
|
@ -3119,24 +3554,22 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"license": "MIT",
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
|
|
@ -3297,16 +3730,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.2",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
|
@ -3344,6 +3776,45 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.5",
|
||||
"@img/sharp-darwin-x64": "0.33.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
|
||||
"@img/sharp-linux-arm": "0.33.5",
|
||||
"@img/sharp-linux-arm64": "0.33.5",
|
||||
"@img/sharp-linux-s390x": "0.33.5",
|
||||
"@img/sharp-linux-x64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.5",
|
||||
"@img/sharp-wasm32": "0.33.5",
|
||||
"@img/sharp-win32-ia32": "0.33.5",
|
||||
"@img/sharp-win32-x64": "0.33.5"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
|
|
@ -3391,6 +3862,15 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"dev": true,
|
||||
|
|
@ -3586,8 +4066,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "5.1.1",
|
||||
"license": "MIT",
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
||||
"dependencies": {
|
||||
"client-only": "0.0.1"
|
||||
},
|
||||
|
|
@ -3595,7 +4076,7 @@
|
|||
"node": ">= 12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
|
||||
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
|
|
@ -3628,6 +4109,18 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/swr": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz",
|
||||
"integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"dev": true,
|
||||
|
|
@ -3675,8 +4168,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"license": "0BSD"
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
|
|
@ -3808,6 +4302,14 @@
|
|||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"funding": [
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth0/nextjs-auth0": "^4.6.0",
|
||||
"classnames": "^2.5.1",
|
||||
"next": "14.2.3",
|
||||
"next": "15.2.3",
|
||||
"ohmy-ui": "^0.0.6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
16
cognee-frontend/src/app/(auth_default)/AuthPage.module.css
Normal file
16
cognee-frontend/src/app/(auth_default)/AuthPage.module.css
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
.main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.authContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: 24px 0;
|
||||
margin: 0 auto;
|
||||
max-width: 440px;
|
||||
width: 100%;
|
||||
}
|
||||
29
cognee-frontend/src/app/(auth_default)/AuthPage.tsx
Normal file
29
cognee-frontend/src/app/(auth_default)/AuthPage.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Spacer, Stack, Text } from 'ohmy-ui';
|
||||
import { TextLogo } from '@/ui/App';
|
||||
import { Divider } from '@/ui/Layout';
|
||||
import Footer from '@/ui/Partials/Footer/Footer';
|
||||
import SignInForm from '@/ui/Partials/SignInForm/SignInForm';
|
||||
|
||||
import styles from './AuthPage.module.css';
|
||||
|
||||
export default function AuthPage() {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<Spacer inset vertical="2" horizontal="2">
|
||||
<Stack orientation="horizontal" gap="between" align="center">
|
||||
<TextLogo width={158} height={44} color="white" />
|
||||
</Stack>
|
||||
</Spacer>
|
||||
<Divider />
|
||||
<div className={styles.authContainer}>
|
||||
<Stack gap="4" style={{ width: '100%' }}>
|
||||
<h1><Text size="large">Sign in</Text></h1>
|
||||
<SignInForm />
|
||||
</Stack>
|
||||
</div>
|
||||
<Spacer inset horizontal="3" wrap>
|
||||
<Footer />
|
||||
</Spacer>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
1
cognee-frontend/src/app/(auth_default)/page.tsx
Normal file
1
cognee-frontend/src/app/(auth_default)/page.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AuthPage';
|
||||
|
|
@ -1,12 +1,17 @@
|
|||
import { Spacer, Stack, Text } from 'ohmy-ui';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { CTAButton, Spacer, Stack, Text } from 'ohmy-ui';
|
||||
import { auth0 } from '@/modules/auth/auth0';
|
||||
import { TextLogo } from '@/ui/App';
|
||||
import { Divider } from '@/ui/Layout';
|
||||
import Footer from '@/ui/Partials/Footer/Footer';
|
||||
import AuthToken from './token/AuthToken';
|
||||
|
||||
import styles from './AuthPage.module.css';
|
||||
import { Divider } from '@/ui/Layout';
|
||||
import SignInForm from '@/ui/Partials/SignInForm/SignInForm';
|
||||
|
||||
export default function AuthPage() {
|
||||
export default async function AuthPage() {
|
||||
const session = await auth0.getSession();
|
||||
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<Spacer inset vertical="2" horizontal="2">
|
||||
|
|
@ -17,8 +22,32 @@ export default function AuthPage() {
|
|||
<Divider />
|
||||
<div className={styles.authContainer}>
|
||||
<Stack gap="4" style={{ width: '100%' }}>
|
||||
<h1><Text size="large">Sign in</Text></h1>
|
||||
<SignInForm />
|
||||
<h1><Text size="large">Welcome to cognee</Text></h1>
|
||||
{session ? (
|
||||
<Stack gap="4">
|
||||
<Text>Hello, {session.user.name}!</Text>
|
||||
<AuthToken />
|
||||
<Link href="/auth/logout">
|
||||
<CTAButton>
|
||||
Log out
|
||||
</CTAButton>
|
||||
</Link>
|
||||
</Stack>
|
||||
) : (
|
||||
<>
|
||||
<Link href="/auth/login?screen_hint=signup">
|
||||
<CTAButton>
|
||||
Sign up
|
||||
</CTAButton>
|
||||
</Link>
|
||||
|
||||
<Link href="/auth/login">
|
||||
<CTAButton>
|
||||
Log in
|
||||
</CTAButton>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
<Spacer inset horizontal="3" wrap>
|
||||
|
|
|
|||
|
|
@ -1 +1,12 @@
|
|||
export { default } from './AuthPage';
|
||||
import Auth0AuthPage from "./AuthPage";
|
||||
import DefaultAuthPage from "../(auth_default)/AuthPage";
|
||||
|
||||
let AuthPage = null;
|
||||
|
||||
if (process.env.USE_AUTH0_AUTHORIZATION === "true") {
|
||||
AuthPage = Auth0AuthPage;
|
||||
} else {
|
||||
AuthPage = DefaultAuthPage;
|
||||
}
|
||||
|
||||
export default AuthPage;
|
||||
|
|
|
|||
14
cognee-frontend/src/app/auth/token/AuthToken.tsx
Normal file
14
cognee-frontend/src/app/auth/token/AuthToken.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function AuthToken() {
|
||||
useEffect(() => {
|
||||
async function get_token() {
|
||||
await fetch("http://localhost:3000/auth/token");
|
||||
}
|
||||
get_token();
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
11
cognee-frontend/src/app/auth/token/route.ts
Normal file
11
cognee-frontend/src/app/auth/token/route.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { auth0 } from "@/modules/auth/auth0";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const accessToken = await auth0.getAccessToken();
|
||||
|
||||
const response = new Response();
|
||||
|
||||
response.headers.set("Set-Cookie", `${process.env.AUTH_TOKEN_COOKIE_NAME}=${accessToken.token}; Expires=${new Date(accessToken.expiresAt * 1000).toUTCString()}; Path=/; SameSite=Lax; Domain=localhost; HttpOnly`);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import getDatasetData from '@/modules/datasets/getDatasetData';
|
|||
import { Footer, SettingsModal } from '@/ui/Partials';
|
||||
import { TextLogo } from '@/ui/App';
|
||||
import { SettingsIcon } from '@/ui/Icons';
|
||||
import AuthToken from './auth/token/AuthToken';
|
||||
|
||||
export default function Home() {
|
||||
const {
|
||||
|
|
@ -76,6 +77,7 @@ export default function Home() {
|
|||
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<AuthToken />
|
||||
<Spacer inset vertical="2" horizontal="2">
|
||||
<Stack orientation="horizontal" gap="between" align="center">
|
||||
<TextLogo width={158} height={44} color="white" />
|
||||
|
|
|
|||
28
cognee-frontend/src/middleware.ts
Normal file
28
cognee-frontend/src/middleware.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { NextResponse, type NextRequest } from "next/server";
|
||||
import { auth0 } from "./modules/auth/auth0";
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
if (process.env.USE_AUTH0_AUTHORIZATION === "true") {
|
||||
if (request.nextUrl.pathname === "/auth/token") {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
const response: NextResponse = await auth0.middleware(request);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
|
||||
*/
|
||||
"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
|
||||
],
|
||||
};
|
||||
8
cognee-frontend/src/modules/auth/auth0.ts
Normal file
8
cognee-frontend/src/modules/auth/auth0.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Auth0Client } from "@auth0/nextjs-auth0/server";
|
||||
|
||||
export const auth0 = new Auth0Client({
|
||||
authorizationParameters: {
|
||||
scope: "openid profile email",
|
||||
audience: "cognee:api",
|
||||
},
|
||||
});
|
||||
|
|
@ -17,11 +17,6 @@ function useDatasets() {
|
|||
const fetchDatasetStatuses = useCallback((datasets: Dataset[]) => {
|
||||
fetch(
|
||||
`/v1/datasets/status?dataset=${datasets.map(d => d.id).join('&dataset=')}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('access_token')}`,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((statuses) => setDatasets(
|
||||
|
|
@ -73,11 +68,7 @@ function useDatasets() {
|
|||
}, []);
|
||||
|
||||
const fetchDatasets = useCallback(() => {
|
||||
fetch('/v1/datasets', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('access_token')}`,
|
||||
},
|
||||
})
|
||||
fetch('/v1/datasets')
|
||||
.then((response) => response.json())
|
||||
.then((datasets) => {
|
||||
setDatasets(datasets);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
useBoolean,
|
||||
} from 'ohmy-ui';
|
||||
import { LoadingIndicator } from '@/ui/App';
|
||||
import { fetch, handleServerErrors } from '@/utils';
|
||||
import { fetch } from '@/utils';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface SignInFormPayload extends HTMLFormElement {
|
||||
|
|
@ -50,10 +50,7 @@ export default function SignInForm({ onSignInSuccess = () => window.location.hre
|
|||
method: 'POST',
|
||||
body: authCredentials,
|
||||
})
|
||||
.then(handleServerErrors)
|
||||
.then(response => response.json())
|
||||
.then((bearer) => {
|
||||
window.localStorage.setItem('access_token', bearer.access_token);
|
||||
.then(() => {
|
||||
onSignInSuccess();
|
||||
})
|
||||
.catch(error => setSignInError(errorsMap[error.detail as keyof typeof errorsMap]))
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import handleServerErrors from './handleServerErrors';
|
||||
import handleServerErrors from "./handleServerErrors";
|
||||
|
||||
export default function fetch(url: string, options: RequestInit = {}): Promise<Response> {
|
||||
return global.fetch('http://localhost:8000/api' + url, {
|
||||
export default async function fetch(url: string, options: RequestInit = {}): Promise<Response> {
|
||||
return global.fetch("http://localhost:8000/api" + url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then(handleServerErrors);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function handleServerErrors(response: Response): Promise<Response> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/auth';
|
||||
return;
|
||||
return redirect("/auth");
|
||||
}
|
||||
if (!response.ok) {
|
||||
return response.json().then(error => reject(error));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
|
@ -18,9 +22,19 @@
|
|||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
"""FastAPI server for the Cognee API."""
|
||||
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
from cognee.shared.logging_utils import get_logger
|
||||
import sentry_sdk
|
||||
from traceback import format_exc
|
||||
from fastapi import Request
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi import FastAPI, status
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from cognee.shared.logging_utils import get_logger
|
||||
from cognee.api.v1.permissions.routers import get_permissions_router
|
||||
from cognee.api.v1.settings.routers import get_settings_router
|
||||
from cognee.api.v1.datasets.routers import get_datasets_router
|
||||
|
|
@ -15,11 +21,7 @@ from cognee.api.v1.search.routers import get_search_router
|
|||
from cognee.api.v1.add.routers import get_add_router
|
||||
from cognee.api.v1.delete.routers import get_delete_router
|
||||
from cognee.api.v1.responses.routers import get_responses_router
|
||||
from fastapi import Request
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from cognee.exceptions import CogneeApiError
|
||||
from traceback import format_exc
|
||||
from cognee.api.v1.users.routers import (
|
||||
get_auth_router,
|
||||
get_register_router,
|
||||
|
|
@ -63,9 +65,10 @@ async def lifespan(app: FastAPI):
|
|||
|
||||
app = FastAPI(debug=app_environment != "prod", lifespan=lifespan)
|
||||
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=["http://localhost:3000", "http://localhost:8000", "https://cognee.eu.auth0.com"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["OPTIONS", "GET", "POST", "DELETE"],
|
||||
allow_headers=["*"],
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from uuid import UUID
|
||||
from typing import Union, BinaryIO, List, Optional
|
||||
|
||||
from cognee.modules.pipelines import Task
|
||||
|
|
@ -11,9 +12,21 @@ async def add(
|
|||
dataset_name: str = "main_dataset",
|
||||
user: User = None,
|
||||
node_set: Optional[List[str]] = None,
|
||||
vector_db_config: dict = None,
|
||||
graph_db_config: dict = None,
|
||||
dataset_id: UUID = None,
|
||||
):
|
||||
tasks = [Task(resolve_data_directories), Task(ingest_data, dataset_name, user, node_set)]
|
||||
tasks = [
|
||||
Task(resolve_data_directories),
|
||||
Task(ingest_data, dataset_name, user, node_set, dataset_id),
|
||||
]
|
||||
|
||||
await cognee_pipeline(
|
||||
tasks=tasks, datasets=dataset_name, data=data, user=user, pipeline_name="add_pipeline"
|
||||
tasks=tasks,
|
||||
datasets=dataset_id if dataset_id else dataset_name,
|
||||
data=data,
|
||||
user=user,
|
||||
pipeline_name="add_pipeline",
|
||||
vector_db_config=vector_db_config,
|
||||
graph_db_config=graph_db_config,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from uuid import UUID
|
||||
|
||||
from fastapi import Form, UploadFile, Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import APIRouter
|
||||
|
|
@ -20,8 +21,8 @@ def get_add_router() -> APIRouter:
|
|||
@router.post("/", response_model=None)
|
||||
async def add(
|
||||
data: List[UploadFile],
|
||||
datasetName: str = Form(),
|
||||
datasetId: Optional[UUID] = Form(default=None),
|
||||
datasetName: Optional[str] = Form(default=None),
|
||||
user: User = Depends(get_authenticated_user),
|
||||
):
|
||||
"""This endpoint is responsible for adding data to the graph."""
|
||||
|
|
@ -30,19 +31,13 @@ def get_add_router() -> APIRouter:
|
|||
if not datasetId and not datasetName:
|
||||
raise ValueError("Either datasetId or datasetName must be provided.")
|
||||
|
||||
if datasetId and not datasetName:
|
||||
dataset = await get_dataset(user_id=user.id, dataset_id=datasetId)
|
||||
try:
|
||||
datasetName = dataset.name
|
||||
except IndexError:
|
||||
raise ValueError("No dataset found with the provided datasetName.")
|
||||
|
||||
try:
|
||||
if isinstance(data, str) and data.startswith("http"):
|
||||
if "github" in data:
|
||||
# Perform git clone if the URL is from GitHub
|
||||
repo_name = data.split("/")[-1].replace(".git", "")
|
||||
subprocess.run(["git", "clone", data, f".data/{repo_name}"], check=True)
|
||||
# TODO: Update add call with dataset info
|
||||
await cognee_add(
|
||||
"data://.data/",
|
||||
f"{repo_name}",
|
||||
|
|
@ -53,10 +48,10 @@ def get_add_router() -> APIRouter:
|
|||
response.raise_for_status()
|
||||
|
||||
file_data = await response.content()
|
||||
|
||||
# TODO: Update add call with dataset info
|
||||
return await cognee_add(file_data)
|
||||
else:
|
||||
await cognee_add(data, datasetName, user=user)
|
||||
await cognee_add(data, dataset_name=datasetName, user=user, dataset_id=datasetId)
|
||||
except Exception as error:
|
||||
return JSONResponse(status_code=409, content={"error": str(error)})
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from cognee.modules.pipelines.tasks.task import Task
|
|||
from cognee.modules.users.models import User
|
||||
from cognee.shared.data_models import KnowledgeGraph
|
||||
from cognee.tasks.documents import (
|
||||
check_permissions_on_documents,
|
||||
check_permissions_on_dataset,
|
||||
classify_documents,
|
||||
extract_chunks_from_documents,
|
||||
)
|
||||
|
|
@ -31,11 +31,18 @@ async def cognify(
|
|||
chunker=TextChunker,
|
||||
chunk_size: int = None,
|
||||
ontology_file_path: Optional[str] = None,
|
||||
vector_db_config: dict = None,
|
||||
graph_db_config: dict = None,
|
||||
):
|
||||
tasks = await get_default_tasks(user, graph_model, chunker, chunk_size, ontology_file_path)
|
||||
|
||||
return await cognee_pipeline(
|
||||
tasks=tasks, datasets=datasets, user=user, pipeline_name="cognify_pipeline"
|
||||
tasks=tasks,
|
||||
datasets=datasets,
|
||||
user=user,
|
||||
pipeline_name="cognify_pipeline",
|
||||
vector_db_config=vector_db_config,
|
||||
graph_db_config=graph_db_config,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -48,7 +55,7 @@ async def get_default_tasks( # TODO: Find out a better way to do this (Boris's
|
|||
) -> list[Task]:
|
||||
default_tasks = [
|
||||
Task(classify_documents),
|
||||
Task(check_permissions_on_documents, user=user, permissions=["write"]),
|
||||
Task(check_permissions_on_dataset, user=user, permissions=["write"]),
|
||||
Task(
|
||||
extract_chunks_from_documents,
|
||||
max_chunk_size=chunk_size or get_max_chunk_tokens(),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from uuid import UUID
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
from fastapi import Depends
|
||||
|
|
@ -10,6 +11,7 @@ from cognee.shared.data_models import KnowledgeGraph
|
|||
|
||||
class CognifyPayloadDTO(BaseModel):
|
||||
datasets: List[str]
|
||||
dataset_ids: Optional[List[UUID]] = None
|
||||
graph_model: Optional[BaseModel] = KnowledgeGraph
|
||||
|
||||
|
||||
|
|
@ -22,7 +24,9 @@ def get_cognify_router() -> APIRouter:
|
|||
from cognee.api.v1.cognify import cognify as cognee_cognify
|
||||
|
||||
try:
|
||||
await cognee_cognify(payload.datasets, user, payload.graph_model)
|
||||
# Send dataset UUIDs if they are given, if not send dataset names
|
||||
datasets = payload.dataset_ids if payload.dataset_ids else payload.datasets
|
||||
await cognee_cognify(datasets, user, payload.graph_model)
|
||||
except Exception as error:
|
||||
return JSONResponse(status_code=409, content={"error": str(error)})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,66 +1,69 @@
|
|||
from uuid import UUID
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.modules.users.methods import get_authenticated_user
|
||||
|
||||
|
||||
def get_permissions_router() -> APIRouter:
|
||||
permissions_router = APIRouter()
|
||||
|
||||
@permissions_router.post("/roles/{role_id}/permissions")
|
||||
async def give_default_permission_to_role(role_id: UUID, permission_name: str):
|
||||
from cognee.modules.users.permissions.methods import (
|
||||
give_default_permission_to_role as set_default_permission_to_role,
|
||||
@permissions_router.post("/datasets/{principal_id}/")
|
||||
async def give_datasets_permission_to_principal(
|
||||
permission_name: str,
|
||||
dataset_ids: List[UUID],
|
||||
principal_id: UUID,
|
||||
user: User = Depends(get_authenticated_user),
|
||||
):
|
||||
from cognee.modules.users.permissions.methods import authorized_give_permission_on_datasets
|
||||
|
||||
await authorized_give_permission_on_datasets(
|
||||
principal_id,
|
||||
[dataset_id for dataset_id in dataset_ids],
|
||||
permission_name,
|
||||
user.id,
|
||||
)
|
||||
|
||||
await set_default_permission_to_role(role_id, permission_name)
|
||||
|
||||
return JSONResponse(status_code=200, content={"message": "Permission assigned to role"})
|
||||
|
||||
@permissions_router.post("/tenants/{tenant_id}/permissions")
|
||||
async def give_default_permission_to_tenant(tenant_id: UUID, permission_name: str):
|
||||
from cognee.modules.users.permissions.methods import (
|
||||
give_default_permission_to_tenant as set_tenant_default_permissions,
|
||||
return JSONResponse(
|
||||
status_code=200, content={"message": "Permission assigned to principal"}
|
||||
)
|
||||
|
||||
await set_tenant_default_permissions(tenant_id, permission_name)
|
||||
|
||||
return JSONResponse(status_code=200, content={"message": "Permission assigned to tenant"})
|
||||
|
||||
@permissions_router.post("/users/{user_id}/permissions")
|
||||
async def give_default_permission_to_user(user_id: UUID, permission_name: str):
|
||||
from cognee.modules.users.permissions.methods import (
|
||||
give_default_permission_to_user as set_default_permission_to_user,
|
||||
)
|
||||
|
||||
await set_default_permission_to_user(user_id, permission_name)
|
||||
|
||||
return JSONResponse(status_code=200, content={"message": "Permission assigned to user"})
|
||||
|
||||
@permissions_router.post("/roles")
|
||||
async def create_role(
|
||||
role_name: str,
|
||||
tenant_id: UUID,
|
||||
):
|
||||
async def create_role(role_name: str, user: User = Depends(get_authenticated_user)):
|
||||
from cognee.modules.users.roles.methods import create_role as create_role_method
|
||||
|
||||
await create_role_method(role_name=role_name, tenant_id=tenant_id)
|
||||
await create_role_method(role_name=role_name, owner_id=user.id)
|
||||
|
||||
return JSONResponse(status_code=200, content={"message": "Role created for tenant"})
|
||||
|
||||
@permissions_router.post("/users/{user_id}/roles")
|
||||
async def add_user_to_role(user_id: UUID, role_id: UUID):
|
||||
async def add_user_to_role(
|
||||
user_id: UUID, role_id: UUID, user: User = Depends(get_authenticated_user)
|
||||
):
|
||||
from cognee.modules.users.roles.methods import add_user_to_role as add_user_to_role_method
|
||||
|
||||
await add_user_to_role_method(user_id=user_id, role_id=role_id)
|
||||
await add_user_to_role_method(user_id=user_id, role_id=role_id, owner_id=user.id)
|
||||
|
||||
return JSONResponse(status_code=200, content={"message": "User added to role"})
|
||||
|
||||
@permissions_router.post("/users/{user_id}/tenants")
|
||||
async def add_user_to_tenant(
|
||||
user_id: UUID, tenant_id: UUID, user: User = Depends(get_authenticated_user)
|
||||
):
|
||||
from cognee.modules.users.tenants.methods import add_user_to_tenant
|
||||
|
||||
await add_user_to_tenant(user_id=user_id, tenant_id=tenant_id, owner_id=user.id)
|
||||
|
||||
return JSONResponse(status_code=200, content={"message": "User added to tenant"})
|
||||
|
||||
@permissions_router.post("/tenants")
|
||||
async def create_tenant(tenant_name: str):
|
||||
async def create_tenant(tenant_name: str, user: User = Depends(get_authenticated_user)):
|
||||
from cognee.modules.users.tenants.methods import create_tenant as create_tenant_method
|
||||
|
||||
await create_tenant_method(tenant_name=tenant_name)
|
||||
await create_tenant_method(tenant_name=tenant_name, user_id=user.id)
|
||||
|
||||
return JSONResponse(status_code=200, content={"message": "Tenant created."})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from uuid import UUID
|
||||
from typing import Optional, Union
|
||||
from datetime import datetime
|
||||
from fastapi import Depends, APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
|
|
@ -9,8 +10,12 @@ from cognee.modules.search.operations import get_history
|
|||
from cognee.modules.users.methods import get_authenticated_user
|
||||
|
||||
|
||||
# Note: Datasets sent by name will only map to datasets owned by the request sender
|
||||
# To search for datasets not owned by the request sender dataset UUID is needed
|
||||
class SearchPayloadDTO(InDTO):
|
||||
search_type: SearchType
|
||||
datasets: Optional[list[str]] = None
|
||||
dataset_ids: Optional[list[UUID]] = None
|
||||
query: str
|
||||
|
||||
|
||||
|
|
@ -39,7 +44,11 @@ def get_search_router() -> APIRouter:
|
|||
|
||||
try:
|
||||
results = await cognee_search(
|
||||
query_text=payload.query, query_type=payload.search_type, user=user
|
||||
query_text=payload.query,
|
||||
query_type=payload.search_type,
|
||||
user=user,
|
||||
datasets=payload.datasets,
|
||||
dataset_ids=payload.dataset_ids,
|
||||
)
|
||||
|
||||
return results
|
||||
|
|
|
|||
|
|
@ -1,32 +1,43 @@
|
|||
from uuid import UUID
|
||||
from typing import Union, Optional, List, Type
|
||||
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.modules.search.types import SearchType
|
||||
from cognee.modules.users.methods import get_default_user
|
||||
from cognee.modules.search.methods import search as search_function
|
||||
from cognee.modules.data.methods import get_authorized_existing_datasets
|
||||
from cognee.modules.data.exceptions import DatasetNotFoundError
|
||||
|
||||
|
||||
async def search(
|
||||
query_text: str,
|
||||
query_type: SearchType = SearchType.GRAPH_COMPLETION,
|
||||
user: User = None,
|
||||
datasets: Union[list[str], str, None] = None,
|
||||
datasets: Optional[Union[list[str], str]] = None,
|
||||
dataset_ids: Optional[Union[list[UUID], UUID]] = None,
|
||||
system_prompt_path: str = "answer_simple_question.txt",
|
||||
top_k: int = 10,
|
||||
node_type: Optional[Type] = None,
|
||||
node_name: Optional[List[str]] = None,
|
||||
) -> list:
|
||||
# We use lists from now on for datasets
|
||||
if isinstance(datasets, str):
|
||||
if isinstance(datasets, UUID) or isinstance(datasets, str):
|
||||
datasets = [datasets]
|
||||
|
||||
if user is None:
|
||||
user = await get_default_user()
|
||||
|
||||
# Transform string based datasets to UUID - String based datasets can only be found for current user
|
||||
if datasets is not None and [all(isinstance(dataset, str) for dataset in datasets)]:
|
||||
datasets = await get_authorized_existing_datasets(datasets, "read", user)
|
||||
datasets = [dataset.id for dataset in datasets]
|
||||
if not datasets:
|
||||
raise DatasetNotFoundError(message="No datasets found.")
|
||||
|
||||
filtered_search_results = await search_function(
|
||||
query_text=query_text,
|
||||
query_type=query_type,
|
||||
datasets=datasets,
|
||||
dataset_ids=dataset_ids if dataset_ids else datasets,
|
||||
user=user,
|
||||
system_prompt_path=system_prompt_path,
|
||||
top_k=top_k,
|
||||
|
|
|
|||
67
cognee/context_global_variables.py
Normal file
67
cognee/context_global_variables.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import os
|
||||
import pathlib
|
||||
from contextvars import ContextVar
|
||||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from cognee.infrastructure.databases.utils import get_or_create_dataset_database
|
||||
from cognee.modules.users.methods import get_user
|
||||
|
||||
# Note: ContextVar allows us to use different graph db configurations in Cognee
|
||||
# for different async tasks, threads and processes
|
||||
vector_db_config = ContextVar("vector_db_config", default=None)
|
||||
graph_db_config = ContextVar("graph_db_config", default=None)
|
||||
|
||||
|
||||
async def set_database_global_context_variables(dataset: Union[str, UUID], user_id: UUID):
|
||||
"""
|
||||
If backend access control is enabled this function will ensure all datasets have their own databases,
|
||||
access to which will be enforced by given permissions.
|
||||
Database name will be determined by dataset_id and LanceDB and KuzuDB use will be enforced.
|
||||
|
||||
Note: This is only currently supported by the following databases:
|
||||
Relational: SQLite, Postgres
|
||||
Vector: LanceDB
|
||||
Graph: KuzuDB
|
||||
|
||||
Args:
|
||||
dataset: Cognee dataset name or id
|
||||
user_id: UUID of the owner of the dataset
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
if not os.getenv("ENABLE_BACKEND_ACCESS_CONTROL", "false").lower() == "true":
|
||||
return
|
||||
|
||||
user = await get_user(user_id)
|
||||
|
||||
# To ensure permissions are enforced properly all datasets will have their own databases
|
||||
dataset_database = await get_or_create_dataset_database(dataset, user)
|
||||
|
||||
# TODO: Find better location for database files
|
||||
cognee_directory_path = str(
|
||||
pathlib.Path(
|
||||
os.path.join(pathlib.Path(__file__).parent, f".cognee_system/databases/{user.id}")
|
||||
).resolve()
|
||||
)
|
||||
|
||||
# Set vector and graph database configuration based on dataset database information
|
||||
vector_config = {
|
||||
"vector_db_url": os.path.join(cognee_directory_path, dataset_database.vector_database_name),
|
||||
"vector_db_key": "",
|
||||
"vector_db_provider": "lancedb",
|
||||
}
|
||||
|
||||
graph_config = {
|
||||
"graph_database_provider": "kuzu",
|
||||
"graph_file_path": os.path.join(
|
||||
cognee_directory_path, dataset_database.graph_database_name
|
||||
),
|
||||
}
|
||||
|
||||
# Use ContextVar to use these graph and vector configurations are used
|
||||
# in the current async context across Cognee
|
||||
graph_db_config.set(graph_config)
|
||||
vector_db_config.set(vector_config)
|
||||
|
|
@ -8,7 +8,7 @@ from cognee.modules.users.models import User
|
|||
from cognee.shared.data_models import KnowledgeGraph
|
||||
from cognee.shared.utils import send_telemetry
|
||||
from cognee.tasks.documents import (
|
||||
check_permissions_on_documents,
|
||||
check_permissions_on_dataset,
|
||||
classify_documents,
|
||||
extract_chunks_from_documents,
|
||||
)
|
||||
|
|
@ -31,7 +31,7 @@ async def get_cascade_graph_tasks(
|
|||
cognee_config = get_cognify_config()
|
||||
default_tasks = [
|
||||
Task(classify_documents),
|
||||
Task(check_permissions_on_documents, user=user, permissions=["write"]),
|
||||
Task(check_permissions_on_dataset, user=user, permissions=["write"]),
|
||||
Task(
|
||||
extract_chunks_from_documents, max_chunk_tokens=get_max_chunk_tokens()
|
||||
), # Extract text chunks based on the document type.
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ class CogneeApiError(Exception):
|
|||
|
||||
super().__init__(self.message, self.name)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}: {self.message} (Status code: {self.status_code})"
|
||||
|
||||
|
||||
class ServiceError(CogneeApiError):
|
||||
"""Failures in external services or APIs, like a database or a third-party service"""
|
||||
|
|
|
|||
|
|
@ -105,3 +105,14 @@ def get_graph_config():
|
|||
- GraphConfig: A GraphConfig instance containing the graph configuration settings.
|
||||
"""
|
||||
return GraphConfig()
|
||||
|
||||
|
||||
def get_graph_context_config():
|
||||
"""This function will get the appropriate graph db config based on async context.
|
||||
This allows the use of multiple graph databases for different threads, async tasks and parallelization
|
||||
"""
|
||||
from cognee.context_global_variables import graph_db_config
|
||||
|
||||
if graph_db_config.get():
|
||||
return graph_db_config.get()
|
||||
return get_graph_config().to_hashable_dict()
|
||||
|
|
|
|||
|
|
@ -2,36 +2,22 @@
|
|||
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
from .config import get_graph_config
|
||||
from .config import get_graph_context_config
|
||||
from .graph_db_interface import GraphDBInterface
|
||||
from .supported_databases import supported_databases
|
||||
|
||||
|
||||
async def get_graph_engine() -> GraphDBInterface:
|
||||
"""
|
||||
Factory function to get the appropriate graph client based on the graph type.
|
||||
"""Factory function to get the appropriate graph client based on the graph type."""
|
||||
# Get appropriate graph configuration based on current async context
|
||||
config = get_graph_context_config()
|
||||
|
||||
This function retrieves the graph configuration and creates a graph engine by calling
|
||||
the `create_graph_engine` function. If the configured graph database provider is
|
||||
'networkx', it ensures that the graph is loaded from a file asynchronously if it hasn't
|
||||
been loaded yet. It raises an `EnvironmentError` if the necessary configurations for the
|
||||
selected graph provider are missing.
|
||||
|
||||
Returns:
|
||||
--------
|
||||
|
||||
- GraphDBInterface: Returns an instance of GraphDBInterface which represents the
|
||||
selected graph client.
|
||||
"""
|
||||
config = get_graph_config()
|
||||
|
||||
graph_client = create_graph_engine(**get_graph_config().to_hashable_dict())
|
||||
graph_client = create_graph_engine(**config)
|
||||
|
||||
# Async functions can't be cached. After creating and caching the graph engine
|
||||
# handle all necessary async operations for different graph types bellow.
|
||||
# Handle loading of graph for NetworkX
|
||||
if config.graph_database_provider.lower() == "networkx" and graph_client.graph is None:
|
||||
if config["graph_database_provider"].lower() == "networkx" and graph_client.graph is None:
|
||||
await graph_client.load_graph_from_file()
|
||||
|
||||
return graph_client
|
||||
|
|
@ -40,11 +26,11 @@ async def get_graph_engine() -> GraphDBInterface:
|
|||
@lru_cache
|
||||
def create_graph_engine(
|
||||
graph_database_provider,
|
||||
graph_database_url,
|
||||
graph_database_username,
|
||||
graph_database_password,
|
||||
graph_database_port,
|
||||
graph_file_path,
|
||||
graph_database_url="",
|
||||
graph_database_username="",
|
||||
graph_database_password="",
|
||||
graph_database_port="",
|
||||
):
|
||||
"""
|
||||
Create a graph engine based on the specified provider type.
|
||||
|
|
|
|||
1
cognee/infrastructure/databases/utils/__init__.py
Normal file
1
cognee/infrastructure/databases/utils/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .get_or_create_dataset_database import get_or_create_dataset_database
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
from uuid import UUID
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from cognee.modules.data.methods import create_dataset
|
||||
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from cognee.modules.data.methods import get_unique_dataset_id
|
||||
from cognee.modules.users.models import DatasetDatabase
|
||||
from cognee.modules.users.models import User
|
||||
|
||||
|
||||
async def get_or_create_dataset_database(
|
||||
dataset: Union[str, UUID],
|
||||
user: User,
|
||||
) -> DatasetDatabase:
|
||||
"""
|
||||
Return the `DatasetDatabase` row for the given owner + dataset.
|
||||
|
||||
• If the row already exists, it is fetched and returned.
|
||||
• Otherwise a new one is created atomically and returned.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
user : User
|
||||
Principal that owns this dataset.
|
||||
dataset : Union[str, UUID]
|
||||
Dataset being linked.
|
||||
"""
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
dataset_id = await get_unique_dataset_id(dataset, user)
|
||||
|
||||
vector_db_name = f"{dataset_id}.lance.db"
|
||||
graph_db_name = f"{dataset_id}.pkl"
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
# Create dataset if it doesn't exist
|
||||
if isinstance(dataset, str):
|
||||
dataset = await create_dataset(dataset, user, session)
|
||||
|
||||
# Try to fetch an existing row first
|
||||
stmt = select(DatasetDatabase).where(
|
||||
DatasetDatabase.owner_id == user.id,
|
||||
DatasetDatabase.dataset_id == dataset_id,
|
||||
)
|
||||
existing: DatasetDatabase = await session.scalar(stmt)
|
||||
if existing:
|
||||
return existing
|
||||
|
||||
# If there are no existing rows build a new row
|
||||
record = DatasetDatabase(
|
||||
owner_id=user.id,
|
||||
dataset_id=dataset_id,
|
||||
vector_database_name=vector_db_name,
|
||||
graph_database_name=graph_db_name,
|
||||
)
|
||||
|
||||
try:
|
||||
session.add(record)
|
||||
await session.commit()
|
||||
await session.refresh(record)
|
||||
return record
|
||||
|
||||
except IntegrityError:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
|
@ -62,3 +62,12 @@ def get_vectordb_config():
|
|||
configuration.
|
||||
"""
|
||||
return VectorConfig()
|
||||
|
||||
|
||||
def get_vectordb_context_config():
|
||||
"""This function will get the appropriate vector db config based on async context."""
|
||||
from cognee.context_global_variables import vector_db_config
|
||||
|
||||
if vector_db_config.get():
|
||||
return vector_db_config.get()
|
||||
return get_vectordb_config().to_dict()
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ from functools import lru_cache
|
|||
|
||||
@lru_cache
|
||||
def create_vector_engine(
|
||||
vector_db_url: str,
|
||||
vector_db_port: str,
|
||||
vector_db_key: str,
|
||||
vector_db_provider: str,
|
||||
vector_db_url: str,
|
||||
vector_db_port: str = "",
|
||||
vector_db_key: str = "",
|
||||
):
|
||||
"""
|
||||
Create a vector database engine based on the specified provider.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
from .config import get_vectordb_config
|
||||
from .config import get_vectordb_context_config
|
||||
from .create_vector_engine import create_vector_engine
|
||||
|
||||
|
||||
def get_vector_engine():
|
||||
"""
|
||||
Create and return a vector engine instance.
|
||||
|
||||
Returns:
|
||||
--------
|
||||
|
||||
A vector engine instance created from the vector database configuration.
|
||||
"""
|
||||
return create_vector_engine(**get_vectordb_config().to_dict())
|
||||
# Get appropriate vector db configuration based on current async context
|
||||
return create_vector_engine(**get_vectordb_context_config())
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
from ..get_vector_engine import get_vector_engine, get_vectordb_config
|
||||
from ..get_vector_engine import get_vector_engine, get_vectordb_context_config
|
||||
from sqlalchemy import text
|
||||
from cognee.context_global_variables import vector_db_config as context_vector_db_config
|
||||
|
||||
|
||||
async def create_db_and_tables():
|
||||
"""
|
||||
Create the database and its associated tables if necessary.
|
||||
|
||||
This function checks the vector database provider configuration and, if it is set to
|
||||
"pgvector", creates the necessary vector extension in the PostgreSQL database using an
|
||||
asynchronous context manager.
|
||||
"""
|
||||
vector_config = get_vectordb_config()
|
||||
# Get appropriate vector db configuration based on current async context
|
||||
vector_config = get_vectordb_context_config()
|
||||
vector_engine = get_vector_engine()
|
||||
|
||||
if vector_config.vector_db_provider == "pgvector":
|
||||
if vector_config["vector_db_provider"] == "pgvector":
|
||||
async with vector_engine.engine.begin() as connection:
|
||||
await connection.execute(text("CREATE EXTENSION IF NOT EXISTS vector;"))
|
||||
|
|
|
|||
|
|
@ -7,4 +7,6 @@ This module defines a set of exceptions for handling various data errors
|
|||
from .exceptions import (
|
||||
UnstructuredLibraryImportError,
|
||||
UnauthorizedDataAccessError,
|
||||
DatasetNotFoundError,
|
||||
DatasetTypeError,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,3 +20,23 @@ class UnauthorizedDataAccessError(CogneeApiError):
|
|||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
):
|
||||
super().__init__(message, name, status_code)
|
||||
|
||||
|
||||
class DatasetNotFoundError(CogneeApiError):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Dataset not found.",
|
||||
name: str = "DatasetNotFoundError",
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
):
|
||||
super().__init__(message, name, status_code)
|
||||
|
||||
|
||||
class DatasetTypeError(CogneeApiError):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Dataset type not supported.",
|
||||
name: str = "DatasetTypeError",
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
):
|
||||
super().__init__(message, name, status_code)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@ from .get_datasets_by_name import get_datasets_by_name
|
|||
from .get_dataset_data import get_dataset_data
|
||||
from .get_data import get_data
|
||||
from .get_unique_dataset_id import get_unique_dataset_id
|
||||
from .get_authorized_existing_datasets import get_authorized_existing_datasets
|
||||
from .get_dataset_ids import get_dataset_ids
|
||||
|
||||
# Delete
|
||||
from .delete_dataset import delete_dataset
|
||||
from .delete_data import delete_data
|
||||
|
||||
# Create
|
||||
from .load_or_create_datasets import load_or_create_datasets
|
||||
|
||||
# Check
|
||||
from .check_dataset_name import check_dataset_name
|
||||
|
|
|
|||
3
cognee/modules/data/methods/check_dataset_name.py
Normal file
3
cognee/modules/data/methods/check_dataset_name.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
def check_dataset_name(dataset_name: str):
|
||||
if "." in dataset_name or " " in dataset_name:
|
||||
raise ValueError("Dataset name cannot contain spaces or underscores")
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
from uuid import UUID, uuid5, NAMESPACE_OID
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from cognee.modules.data.models import Dataset
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.modules.data.methods.get_dataset_ids import get_dataset_ids
|
||||
from cognee.modules.users.permissions.methods import get_all_user_permission_datasets
|
||||
from cognee.modules.users.permissions.methods import get_specific_user_permission_datasets
|
||||
|
||||
|
||||
async def get_authorized_existing_datasets(
|
||||
datasets: Union[list[str], list[UUID]], permission_type: str, user: User
|
||||
) -> list[Dataset]:
|
||||
"""
|
||||
Function returns a list of existing dataset objects user has access for based on datasets input.
|
||||
|
||||
Args:
|
||||
datasets:
|
||||
user:
|
||||
|
||||
Returns:
|
||||
list of Dataset objects
|
||||
|
||||
"""
|
||||
if datasets:
|
||||
# Function handles transforming dataset input to dataset IDs (if possible)
|
||||
dataset_ids = await get_dataset_ids(datasets, user)
|
||||
# If dataset_ids are provided filter these datasets based on what user has permission for.
|
||||
if dataset_ids:
|
||||
existing_datasets = await get_specific_user_permission_datasets(
|
||||
user.id, permission_type, dataset_ids
|
||||
)
|
||||
else:
|
||||
existing_datasets = []
|
||||
else:
|
||||
# If no datasets are provided, work with all existing datasets user has permission for.
|
||||
existing_datasets = await get_all_user_permission_datasets(user, permission_type)
|
||||
|
||||
return existing_datasets
|
||||
36
cognee/modules/data/methods/get_dataset_ids.py
Normal file
36
cognee/modules/data/methods/get_dataset_ids.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from cognee.modules.data.exceptions import DatasetTypeError
|
||||
from cognee.modules.data.methods import get_datasets
|
||||
|
||||
|
||||
async def get_dataset_ids(datasets: Union[list[str], list[UUID]], user):
|
||||
"""
|
||||
Function returns dataset IDs necessary based on provided input.
|
||||
It transforms raw strings into real dataset_ids with keeping write permissions in mind.
|
||||
If a user wants to write to a dataset he is not the owner of it must be provided through UUID.
|
||||
Args:
|
||||
datasets:
|
||||
pipeline_name:
|
||||
user:
|
||||
|
||||
Returns: a list of write access dataset_ids if they exist
|
||||
|
||||
"""
|
||||
if all(isinstance(dataset, UUID) for dataset in datasets):
|
||||
# Return list of dataset UUIDs
|
||||
dataset_ids = datasets
|
||||
else:
|
||||
# Convert list of dataset names to dataset UUID
|
||||
if all(isinstance(dataset, str) for dataset in datasets):
|
||||
# Get all user owned dataset objects (If a user wants to write to a dataset he is not the owner of it must be provided through UUID.)
|
||||
user_datasets = await get_datasets(user.id)
|
||||
# Filter out non name mentioned datasets
|
||||
dataset_ids = [dataset.id for dataset in user_datasets if dataset.name in datasets]
|
||||
else:
|
||||
raise DatasetTypeError(
|
||||
f"One or more of the provided dataset types is not handled: f{datasets}"
|
||||
)
|
||||
|
||||
return dataset_ids
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
from uuid import UUID, uuid5, NAMESPACE_OID
|
||||
from cognee.modules.users.models import User
|
||||
from typing import Union
|
||||
|
||||
|
||||
async def get_unique_dataset_id(dataset_name: str, user: User) -> UUID:
|
||||
async def get_unique_dataset_id(dataset_name: Union[str, UUID], user: User) -> UUID:
|
||||
if isinstance(dataset_name, UUID):
|
||||
return dataset_name
|
||||
return uuid5(NAMESPACE_OID, f"{dataset_name}{str(user.id)}")
|
||||
|
|
|
|||
42
cognee/modules/data/methods/load_or_create_datasets.py
Normal file
42
cognee/modules/data/methods/load_or_create_datasets.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from typing import List, Union
|
||||
from uuid import UUID
|
||||
|
||||
from cognee.modules.data.models import Dataset
|
||||
from cognee.modules.data.methods import get_unique_dataset_id
|
||||
from cognee.modules.data.exceptions import DatasetNotFoundError
|
||||
|
||||
|
||||
async def load_or_create_datasets(
|
||||
dataset_names: List[Union[str, UUID]], existing_datasets: List[Dataset], user
|
||||
) -> List[Dataset]:
|
||||
"""
|
||||
Given a list of dataset identifiers (names or UUIDs), return Dataset instances:
|
||||
- If an identifier matches an existing Dataset (by name or id), reuse it.
|
||||
- Otherwise, create a new Dataset with a unique id. Note: Created dataset is not stored to database.
|
||||
"""
|
||||
result: List[Dataset] = []
|
||||
|
||||
for identifier in dataset_names:
|
||||
# Try to find a matching dataset in the existing list
|
||||
# If no matching dataset is found return None
|
||||
match = next(
|
||||
(ds for ds in existing_datasets if ds.name == identifier or ds.id == identifier), None
|
||||
)
|
||||
|
||||
if match:
|
||||
result.append(match)
|
||||
continue
|
||||
|
||||
# If the identifier is a UUID but nothing matched, that's an error
|
||||
if isinstance(identifier, UUID):
|
||||
raise DatasetNotFoundError(f"Dataset with given UUID does not exist: {identifier}")
|
||||
|
||||
# Otherwise, create a new Dataset instance
|
||||
new_dataset = Dataset(
|
||||
id=await get_unique_dataset_id(dataset_name=identifier, user=user),
|
||||
name=identifier,
|
||||
owner_id=user.id,
|
||||
)
|
||||
result.append(new_dataset)
|
||||
|
||||
return result
|
||||
|
|
@ -33,9 +33,6 @@ class Data(Base):
|
|||
cascade="all, delete",
|
||||
)
|
||||
|
||||
# New relationship for ACLs with cascade deletion
|
||||
acls = relationship("ACL", back_populates="data", cascade="all, delete-orphan")
|
||||
|
||||
def to_json(self) -> dict:
|
||||
return {
|
||||
"id": str(self.id),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ class Dataset(Base):
|
|||
|
||||
owner_id = Column(UUID, index=True)
|
||||
|
||||
acls = relationship("ACL", back_populates="dataset", cascade="all, delete-orphan")
|
||||
|
||||
data: Mapped[List["Data"]] = relationship(
|
||||
"Data",
|
||||
secondary=DatasetData.__tablename__,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import asyncio
|
||||
from typing import Union
|
||||
from uuid import NAMESPACE_OID, uuid5
|
||||
from uuid import NAMESPACE_OID, uuid5, UUID
|
||||
|
||||
from cognee.shared.logging_utils import get_logger
|
||||
from cognee.modules.data.methods import get_datasets
|
||||
from cognee.modules.data.methods.get_dataset_data import get_dataset_data
|
||||
from cognee.modules.data.methods.get_unique_dataset_id import get_unique_dataset_id
|
||||
from cognee.modules.data.models import Data, Dataset
|
||||
from cognee.modules.pipelines.operations.run_tasks import run_tasks
|
||||
from cognee.modules.pipelines.models import PipelineRunStatus
|
||||
|
|
@ -14,6 +12,13 @@ from cognee.modules.pipelines.tasks.task import Task
|
|||
from cognee.modules.users.methods import get_default_user
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.modules.pipelines.operations import log_pipeline_run_initiated
|
||||
from cognee.context_global_variables import set_database_global_context_variables
|
||||
from cognee.modules.data.exceptions import DatasetNotFoundError
|
||||
from cognee.modules.data.methods import (
|
||||
get_authorized_existing_datasets,
|
||||
load_or_create_datasets,
|
||||
check_dataset_name,
|
||||
)
|
||||
|
||||
from cognee.infrastructure.databases.relational import (
|
||||
create_db_and_tables as create_relational_db_and_tables,
|
||||
|
|
@ -21,6 +26,10 @@ from cognee.infrastructure.databases.relational import (
|
|||
from cognee.infrastructure.databases.vector.pgvector import (
|
||||
create_db_and_tables as create_pgvector_db_and_tables,
|
||||
)
|
||||
from cognee.context_global_variables import (
|
||||
graph_db_config as context_graph_db_config,
|
||||
vector_db_config as context_vector_db_config,
|
||||
)
|
||||
|
||||
logger = get_logger("cognee.pipeline")
|
||||
|
||||
|
|
@ -30,10 +39,19 @@ update_status_lock = asyncio.Lock()
|
|||
async def cognee_pipeline(
|
||||
tasks: list[Task],
|
||||
data=None,
|
||||
datasets: Union[str, list[str]] = None,
|
||||
datasets: Union[str, list[str], list[UUID]] = None,
|
||||
user: User = None,
|
||||
pipeline_name: str = "custom_pipeline",
|
||||
vector_db_config: dict = None,
|
||||
graph_db_config: dict = None,
|
||||
):
|
||||
# Note: These context variables allow different value assignment for databases in Cognee
|
||||
# per async task, thread, process and etc.
|
||||
if vector_db_config:
|
||||
context_vector_db_config.set(vector_db_config)
|
||||
if graph_db_config:
|
||||
context_graph_db_config.set(graph_db_config)
|
||||
|
||||
# Create tables for databases
|
||||
await create_relational_db_and_tables()
|
||||
await create_pgvector_db_and_tables()
|
||||
|
|
@ -54,49 +72,35 @@ async def cognee_pipeline(
|
|||
if user is None:
|
||||
user = await get_default_user()
|
||||
|
||||
# Convert datasets to list in case it's a string
|
||||
if isinstance(datasets, str):
|
||||
# Convert datasets to list
|
||||
if isinstance(datasets, str) or isinstance(datasets, UUID):
|
||||
datasets = [datasets]
|
||||
|
||||
# If no datasets are provided, work with all existing datasets.
|
||||
existing_datasets = await get_datasets(user.id)
|
||||
# Get datasets user wants write permissions for (verify user has permissions if datasets are provided as well)
|
||||
# NOTE: If a user wants to write to a dataset he does not own it must be provided through UUID
|
||||
existing_datasets = await get_authorized_existing_datasets(datasets, "write", user)
|
||||
|
||||
if not datasets:
|
||||
# Get datasets from database if none sent.
|
||||
datasets = existing_datasets
|
||||
else:
|
||||
# If dataset is already in database, use it, otherwise create a new instance.
|
||||
dataset_instances = []
|
||||
# If dataset matches an existing Dataset (by name or id), reuse it. Otherwise, create a new Dataset.
|
||||
datasets = await load_or_create_datasets(datasets, existing_datasets, user)
|
||||
|
||||
for dataset_name in datasets:
|
||||
is_dataset_found = False
|
||||
|
||||
for existing_dataset in existing_datasets:
|
||||
if (
|
||||
existing_dataset.name == dataset_name
|
||||
or str(existing_dataset.id) == dataset_name
|
||||
):
|
||||
dataset_instances.append(existing_dataset)
|
||||
is_dataset_found = True
|
||||
break
|
||||
|
||||
if not is_dataset_found:
|
||||
dataset_instances.append(
|
||||
Dataset(
|
||||
id=await get_unique_dataset_id(dataset_name=dataset_name, user=user),
|
||||
name=dataset_name,
|
||||
owner_id=user.id,
|
||||
)
|
||||
)
|
||||
|
||||
datasets = dataset_instances
|
||||
if not datasets:
|
||||
raise DatasetNotFoundError("There are no datasets to work with.")
|
||||
|
||||
awaitables = []
|
||||
|
||||
for dataset in datasets:
|
||||
awaitables.append(
|
||||
run_pipeline(
|
||||
dataset=dataset, user=user, tasks=tasks, data=data, pipeline_name=pipeline_name
|
||||
dataset=dataset,
|
||||
user=user,
|
||||
tasks=tasks,
|
||||
data=data,
|
||||
pipeline_name=pipeline_name,
|
||||
context={"dataset": dataset},
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -109,9 +113,13 @@ async def run_pipeline(
|
|||
tasks: list[Task],
|
||||
data=None,
|
||||
pipeline_name: str = "custom_pipeline",
|
||||
context: dict = None,
|
||||
):
|
||||
check_dataset_name(dataset.name)
|
||||
|
||||
# Will only be used if ENABLE_BACKEND_ACCESS_CONTROL is set to True
|
||||
await set_database_global_context_variables(dataset.name, user.id)
|
||||
|
||||
# Ugly hack, but no easier way to do this.
|
||||
if pipeline_name == "add_pipeline":
|
||||
# Refresh the add pipeline status so data is added to a dataset.
|
||||
|
|
@ -160,15 +168,10 @@ async def run_pipeline(
|
|||
if not isinstance(task, Task):
|
||||
raise ValueError(f"Task {task} is not an instance of Task")
|
||||
|
||||
pipeline_run = run_tasks(tasks, dataset_id, data, user, pipeline_name)
|
||||
pipeline_run = run_tasks(tasks, dataset_id, data, user, pipeline_name, context=context)
|
||||
pipeline_run_status = None
|
||||
|
||||
async for run_status in pipeline_run:
|
||||
pipeline_run_status = run_status
|
||||
|
||||
return pipeline_run_status
|
||||
|
||||
|
||||
def check_dataset_name(dataset_name: str) -> str:
|
||||
if "." in dataset_name or " " in dataset_name:
|
||||
raise ValueError("Dataset name cannot contain spaces or underscores")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import os
|
||||
import json
|
||||
from typing import Callable, Optional, List, Type
|
||||
import asyncio
|
||||
from uuid import UUID
|
||||
from typing import Callable, List, Optional, Type, Union
|
||||
|
||||
from cognee.context_global_variables import set_database_global_context_variables
|
||||
from cognee.exceptions import InvalidValueError
|
||||
from cognee.infrastructure.engine.utils import parse_id
|
||||
from cognee.modules.retrieval.chunks_retriever import ChunksRetriever
|
||||
from cognee.modules.retrieval.insights_retriever import InsightsRetriever
|
||||
from cognee.modules.retrieval.summaries_retriever import SummariesRetriever
|
||||
|
|
@ -21,24 +24,45 @@ from cognee.modules.retrieval.natural_language_retriever import NaturalLanguageR
|
|||
from cognee.modules.search.types import SearchType
|
||||
from cognee.modules.storage.utils import JSONEncoder
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.modules.users.permissions.methods import get_document_ids_for_user
|
||||
from cognee.modules.data.models import Dataset
|
||||
from cognee.shared.utils import send_telemetry
|
||||
from cognee.modules.users.permissions.methods import get_specific_user_permission_datasets
|
||||
from cognee.modules.search.operations import log_query, log_result
|
||||
|
||||
|
||||
async def search(
|
||||
query_text: str,
|
||||
query_type: SearchType,
|
||||
datasets: list[str],
|
||||
dataset_ids: Union[list[UUID], None],
|
||||
user: User,
|
||||
system_prompt_path="answer_simple_question.txt",
|
||||
top_k: int = 10,
|
||||
node_type: Optional[Type] = None,
|
||||
node_name: Optional[List[str]] = None,
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
query_text:
|
||||
query_type:
|
||||
datasets:
|
||||
user:
|
||||
system_prompt_path:
|
||||
top_k:
|
||||
|
||||
Returns:
|
||||
|
||||
Notes:
|
||||
Searching by dataset is only available in ENABLE_BACKEND_ACCESS_CONTROL mode
|
||||
"""
|
||||
# Use search function filtered by permissions if access control is enabled
|
||||
if os.getenv("ENABLE_BACKEND_ACCESS_CONTROL", "false").lower() == "true":
|
||||
return await permissions_search(
|
||||
query_text, query_type, user, dataset_ids, system_prompt_path, top_k
|
||||
)
|
||||
|
||||
query = await log_query(query_text, query_type.value, user.id)
|
||||
|
||||
own_document_ids = await get_document_ids_for_user(user.id, datasets)
|
||||
search_results = await specific_search(
|
||||
query_type,
|
||||
query_text,
|
||||
|
|
@ -49,18 +73,9 @@ async def search(
|
|||
node_name=node_name,
|
||||
)
|
||||
|
||||
filtered_search_results = []
|
||||
await log_result(query.id, json.dumps(search_results, cls=JSONEncoder), user.id)
|
||||
|
||||
for search_result in search_results:
|
||||
document_id = search_result["document_id"] if "document_id" in search_result else None
|
||||
document_id = parse_id(document_id)
|
||||
|
||||
if document_id is None or document_id in own_document_ids:
|
||||
filtered_search_results.append(search_result)
|
||||
|
||||
await log_result(query.id, json.dumps(filtered_search_results, cls=JSONEncoder), user.id)
|
||||
|
||||
return filtered_search_results
|
||||
return search_results
|
||||
|
||||
|
||||
async def specific_search(
|
||||
|
|
@ -120,3 +135,62 @@ async def specific_search(
|
|||
send_telemetry("cognee.search EXECUTION COMPLETED", user.id)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def permissions_search(
|
||||
query_text: str,
|
||||
query_type: SearchType,
|
||||
user: User = None,
|
||||
dataset_ids: Optional[list[UUID]] = None,
|
||||
system_prompt_path: str = "answer_simple_question.txt",
|
||||
top_k: int = 10,
|
||||
) -> list:
|
||||
"""
|
||||
Verifies access for provided datasets or uses all datasets user has read access for and performs search per dataset.
|
||||
Not to be used outside of active access control mode.
|
||||
"""
|
||||
|
||||
query = await log_query(query_text, query_type.value, user.id)
|
||||
|
||||
# Find datasets user has read access for (if datasets are provided only return them. Provided user has read access)
|
||||
search_datasets = await get_specific_user_permission_datasets(user.id, "read", dataset_ids)
|
||||
|
||||
# Searches all provided datasets and handles setting up of appropriate database context based on permissions
|
||||
search_results = await specific_search_by_context(
|
||||
search_datasets, query_text, query_type, user, system_prompt_path, top_k
|
||||
)
|
||||
|
||||
await log_result(query.id, json.dumps(search_results, cls=JSONEncoder), user.id)
|
||||
|
||||
return search_results
|
||||
|
||||
|
||||
async def specific_search_by_context(
|
||||
search_datasets: list[Dataset],
|
||||
query_text: str,
|
||||
query_type: SearchType,
|
||||
user: User,
|
||||
system_prompt_path: str,
|
||||
top_k: int,
|
||||
):
|
||||
"""
|
||||
Searches all provided datasets and handles setting up of appropriate database context based on permissions.
|
||||
Not to be used outside of active access control mode.
|
||||
"""
|
||||
|
||||
async def _search_by_context(dataset, user, query_type, query_text, system_prompt_path, top_k):
|
||||
# Set database configuration in async context for each dataset user has access for
|
||||
await set_database_global_context_variables(dataset.id, dataset.owner_id)
|
||||
search_results = await specific_search(
|
||||
query_type, query_text, user, system_prompt_path=system_prompt_path, top_k=top_k
|
||||
)
|
||||
return {dataset.name: search_results}
|
||||
|
||||
# Search every dataset async based on query and appropriate database configuration
|
||||
tasks = []
|
||||
for dataset in search_datasets:
|
||||
tasks.append(
|
||||
_search_by_context(dataset, user, query_type, query_text, system_prompt_path, top_k)
|
||||
)
|
||||
|
||||
return await asyncio.gather(*tasks)
|
||||
|
|
|
|||
3
cognee/modules/users/authentication/auth0/__init__.py
Normal file
3
cognee/modules/users/authentication/auth0/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from .auth0_config import get_auth0_config
|
||||
from .auth0_jwt_strategy import Auth0JWTStrategy
|
||||
from .auth0_transport import auth0_transport
|
||||
15
cognee/modules/users/authentication/auth0/auth0_client.py
Normal file
15
cognee/modules/users/authentication/auth0/auth0_client.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import os
|
||||
from starlette.config import Config
|
||||
from authlib.integrations.starlette_client import OAuth
|
||||
|
||||
config = Config(".env")
|
||||
|
||||
oauth = OAuth(config)
|
||||
|
||||
oauth.register(
|
||||
"auth0",
|
||||
client_id=os.getenv("AUTH0_CLIENT_ID"),
|
||||
client_secret=os.getenv("AUTH0_CLIENT_SECRET"),
|
||||
server_metadata_url=f"https://{os.getenv('AUTH0_DOMAIN')}/.well-known/openid-configuration",
|
||||
client_kwargs={"scope": "openid profile email"},
|
||||
)
|
||||
22
cognee/modules/users/authentication/auth0/auth0_config.py
Normal file
22
cognee/modules/users/authentication/auth0/auth0_config.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from functools import lru_cache
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Auth0Config(BaseSettings):
|
||||
auth0_domain: str
|
||||
auth0_api_audience: str
|
||||
auth0_algorithms: str
|
||||
auth0_state_secret: str
|
||||
auth0_client_secret: str
|
||||
auth0_client_id: str
|
||||
auth0_issuer: str
|
||||
auth_token_cookie_name: str
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
extra = "allow"
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_auth0_config():
|
||||
return Auth0Config()
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
from fastapi import HTTPException
|
||||
from fastapi_users.schemas import BaseUserCreate
|
||||
from fastapi_users.exceptions import UserNotExists
|
||||
from fastapi_users.authentication import JWTStrategy
|
||||
|
||||
from cognee.modules.users.get_user_manager import UserManager
|
||||
from cognee.modules.users.tenants.methods import create_tenant
|
||||
from cognee.modules.users.authentication.auth0.verify_auth0_token import VerifyAuth0Token
|
||||
|
||||
|
||||
token_verification = VerifyAuth0Token()
|
||||
|
||||
|
||||
class Auth0JWTStrategy(JWTStrategy):
|
||||
async def read_token(self, token: str, user_manager: UserManager):
|
||||
email = token_verification.verify(token)
|
||||
|
||||
if not email:
|
||||
raise HTTPException(status_code=400, detail="Missing email in token")
|
||||
|
||||
try:
|
||||
user = await user_manager.get_by_email(user_email=email)
|
||||
|
||||
return user
|
||||
except UserNotExists:
|
||||
# Auto-provision user
|
||||
|
||||
new_user = BaseUserCreate(
|
||||
email=email,
|
||||
password="NOT IMPORTANT FOR AUTH0"
|
||||
)
|
||||
|
||||
user = await user_manager.create(new_user)
|
||||
await create_tenant(tenant_name="My organization", user_id=user.id)
|
||||
|
||||
return user
|
||||
12
cognee/modules/users/authentication/auth0/auth0_transport.py
Normal file
12
cognee/modules/users/authentication/auth0/auth0_transport.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from fastapi_users.authentication import CookieTransport
|
||||
|
||||
from .auth0_config import get_auth0_config
|
||||
|
||||
|
||||
auth0_transport = CookieTransport(
|
||||
cookie_name=get_auth0_config().auth_token_cookie_name,
|
||||
cookie_httponly=True,
|
||||
cookie_samesite="Lax",
|
||||
)
|
||||
|
||||
auth0_transport.name = "cookie"
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import json
|
||||
from jose import jwt
|
||||
from jose.exceptions import JWTError
|
||||
from urllib.request import urlopen
|
||||
|
||||
from cognee.modules.users.exceptions.exceptions import (
|
||||
UnauthenticatedException,
|
||||
UnauthorizedException,
|
||||
)
|
||||
from .auth0_client import oauth
|
||||
|
||||
from .auth0_config import get_auth0_config
|
||||
|
||||
auth0_config = get_auth0_config()
|
||||
|
||||
|
||||
class VerifyAuth0Token:
|
||||
def __init__(self):
|
||||
# This gets the JWKS from a given URL and does processing so you can
|
||||
# use any of the keys available.
|
||||
jwks_url = urlopen(f"https://{auth0_config.auth0_domain}/.well-known/jwks.json")
|
||||
self.jwks = json.loads(jwks_url.read())
|
||||
|
||||
def verify(self, token: str):
|
||||
try:
|
||||
unverified_header = jwt.get_unverified_header(token)
|
||||
except JWTError:
|
||||
raise UnauthenticatedException(detail="Invalid header")
|
||||
|
||||
rsa_key = None
|
||||
for key in self.jwks["keys"]:
|
||||
if key["kid"] == unverified_header["kid"]:
|
||||
rsa_key = key
|
||||
break
|
||||
|
||||
if not rsa_key:
|
||||
raise UnauthorizedException(status_code=401, detail="Invalid token")
|
||||
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
rsa_key,
|
||||
algorithms=auth0_config.auth0_algorithms,
|
||||
audience=auth0_config.auth0_api_audience,
|
||||
issuer=f"https://{auth0_config.auth0_domain}/"
|
||||
)
|
||||
|
||||
email = payload["email"]
|
||||
|
||||
return email
|
||||
except JWTError as e:
|
||||
raise UnauthorizedException(detail=f"Token decode error: {str(e)}")
|
||||
2
cognee/modules/users/authentication/default/__init__.py
Normal file
2
cognee/modules/users/authentication/default/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from .default_transport import default_transport
|
||||
from .default_jwt_strategy import DefaultJWTStrategy
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import os
|
||||
import jwt
|
||||
from uuid import UUID
|
||||
from fastapi_users.jwt import generate_jwt
|
||||
from fastapi_users.authentication import JWTStrategy
|
||||
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.modules.users.get_user_manager import UserManager
|
||||
|
||||
|
||||
class DefaultJWTStrategy(JWTStrategy):
|
||||
async def read_token(self, token: str, user_manager: UserManager):
|
||||
payload = jwt.decode(
|
||||
token, os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret"), algorithms=["HS256"]
|
||||
)
|
||||
|
||||
user_id = UUID(payload["user_id"])
|
||||
|
||||
return await user_manager.get(user_id)
|
||||
|
||||
|
||||
async def write_token(self, user: User) -> str:
|
||||
# JoinLoad tenant and role information to user object
|
||||
data = {"user_id": str(user.id)}
|
||||
|
||||
return generate_jwt(data, self.encode_key, self.lifetime_seconds, algorithm=self.algorithm)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import os
|
||||
from fastapi_users.authentication import CookieTransport
|
||||
|
||||
default_transport = CookieTransport(
|
||||
cookie_name=os.getenv("AUTH_TOKEN_COOKIE_NAME"),
|
||||
cookie_httponly=True,
|
||||
cookie_samesite="Lax",
|
||||
)
|
||||
|
||||
default_transport.name = "cookie"
|
||||
|
|
@ -1,43 +1,35 @@
|
|||
import os
|
||||
from functools import lru_cache
|
||||
from fastapi_users import models
|
||||
from fastapi_users.jwt import generate_jwt
|
||||
from fastapi_users.authentication import (
|
||||
AuthenticationBackend,
|
||||
BearerTransport,
|
||||
JWTStrategy,
|
||||
)
|
||||
from fastapi_users.authentication import AuthenticationBackend, JWTStrategy
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.modules.users.methods import get_user
|
||||
|
||||
|
||||
class CustomJWTStrategy(JWTStrategy):
|
||||
async def write_token(self, user: User, lifetime_seconds: Optional[int] = None) -> str:
|
||||
# JoinLoad tenant and role information to user object
|
||||
user = await get_user(user.id)
|
||||
|
||||
if user.tenant:
|
||||
data = {"user_id": str(user.id), "tenant_id": str(user.tenant.id), "roles": user.roles}
|
||||
else:
|
||||
# The default tenant is None
|
||||
data = {"user_id": str(user.id), "tenant_id": None, "roles": user.roles}
|
||||
return generate_jwt(data, self.encode_key, self.lifetime_seconds, algorithm=self.algorithm)
|
||||
from .default import default_transport
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_auth_backend():
|
||||
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
||||
transport = default_transport
|
||||
|
||||
if os.getenv("USE_AUTH0_AUTHORIZATION") == "True":
|
||||
from .auth0 import auth0_transport
|
||||
|
||||
transport = auth0_transport
|
||||
|
||||
def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:
|
||||
secret = os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret")
|
||||
return CustomJWTStrategy(secret, lifetime_seconds=3600)
|
||||
if os.getenv("USE_AUTH0_AUTHORIZATION") == "True":
|
||||
from .auth0 import Auth0JWTStrategy
|
||||
|
||||
return Auth0JWTStrategy(secret="NOT IMPORTANT FOR AUTH0", lifetime_seconds=36000) # 10 hours is default token lifetime
|
||||
else:
|
||||
from .default.default_jwt_strategy import DefaultJWTStrategy
|
||||
|
||||
secret = os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret")
|
||||
|
||||
return DefaultJWTStrategy(secret, lifetime_seconds=3600)
|
||||
|
||||
auth_backend = AuthenticationBackend(
|
||||
name="jwt",
|
||||
transport=bearer_transport,
|
||||
name=transport.name,
|
||||
transport=transport,
|
||||
get_strategy=get_jwt_strategy,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ from .exceptions import (
|
|||
UserNotFoundError,
|
||||
PermissionDeniedError,
|
||||
TenantNotFoundError,
|
||||
PermissionNotFoundError,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
from fastapi import HTTPException, status
|
||||
from cognee.exceptions import CogneeApiError
|
||||
from fastapi import status
|
||||
|
||||
|
||||
class UnauthorizedException(HTTPException):
|
||||
def __init__(self, detail: str, **kwargs):
|
||||
"""Returns HTTP 403"""
|
||||
super().__init__(status.HTTP_403_FORBIDDEN, detail=detail)
|
||||
|
||||
|
||||
class UnauthenticatedException(HTTPException):
|
||||
def __init__(self):
|
||||
super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail="Requires authentication")
|
||||
|
||||
|
||||
class RoleNotFoundError(CogneeApiError):
|
||||
|
|
@ -46,3 +57,13 @@ class PermissionDeniedError(CogneeApiError):
|
|||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
):
|
||||
super().__init__(message, name, status_code)
|
||||
|
||||
|
||||
class PermissionNotFoundError(CogneeApiError):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Permission type does not exist.",
|
||||
name: str = "PermissionNotFoundError",
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
):
|
||||
super().__init__(message, name, status_code)
|
||||
|
|
|
|||
|
|
@ -1,37 +1,15 @@
|
|||
import os
|
||||
import uuid
|
||||
from typing import Optional
|
||||
from fastapi import Depends, Request
|
||||
from fastapi_users import BaseUserManager, UUIDIDMixin, models
|
||||
from fastapi_users.db import SQLAlchemyUserDatabase
|
||||
from contextlib import asynccontextmanager
|
||||
from .get_user_db import get_user_db
|
||||
from fastapi import Depends, Request
|
||||
from fastapi_users import BaseUserManager, UUIDIDMixin
|
||||
from fastapi_users.db import SQLAlchemyUserDatabase
|
||||
|
||||
from .models import User
|
||||
from .methods import get_user
|
||||
from fastapi_users.exceptions import UserNotExists
|
||||
from .get_user_db import get_user_db
|
||||
|
||||
|
||||
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
|
||||
reset_password_token_secret = os.getenv(
|
||||
"FASTAPI_USERS_RESET_PASSWORD_TOKEN_SECRET", "super_secret"
|
||||
)
|
||||
verification_token_secret = os.getenv("FASTAPI_USERS_VERIFICATION_TOKEN_SECRET", "super_secret")
|
||||
|
||||
async def get(self, id: models.ID) -> models.UP:
|
||||
"""
|
||||
Get a user by id.
|
||||
|
||||
:param id: Id. of the user to retrieve.
|
||||
:raises UserNotExists: The user does not exist.
|
||||
:return: A user.
|
||||
"""
|
||||
user = await get_user(id)
|
||||
|
||||
if user is None:
|
||||
raise UserNotExists()
|
||||
|
||||
return user
|
||||
|
||||
async def on_after_register(self, user: User, request: Optional[Request] = None):
|
||||
print(f"User {user.id} has registered.")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +1,36 @@
|
|||
from types import SimpleNamespace
|
||||
|
||||
from ..get_fastapi_users import get_fastapi_users
|
||||
from fastapi import HTTPException, Header
|
||||
import os
|
||||
import jwt
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
fastapi_users = get_fastapi_users()
|
||||
|
||||
get_authenticated_user = fastapi_users.current_user(active=True)
|
||||
|
||||
async def get_authenticated_user(authorization: str = Header(...)) -> SimpleNamespace:
|
||||
"""Extract and validate JWT from Authorization header."""
|
||||
try:
|
||||
scheme, token = authorization.split()
|
||||
if scheme.lower() != "bearer":
|
||||
raise HTTPException(status_code=401, detail="Invalid authentication scheme")
|
||||
# # Allows Swagger to understand authorization type and allow single sign on for the Swagger docs to test backend
|
||||
# bearer_scheme = HTTPBearer(scheme_name="BearerAuth", description="Paste **Bearer <JWT>**")
|
||||
|
||||
payload = jwt.decode(
|
||||
token, os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret"), algorithms=["HS256"]
|
||||
)
|
||||
|
||||
if payload["tenant_id"]:
|
||||
# SimpleNamespace lets us access dictionary elements like attributes
|
||||
auth_data = SimpleNamespace(
|
||||
id=UUID(payload["user_id"]),
|
||||
tenant_id=UUID(payload["tenant_id"]),
|
||||
roles=payload["roles"],
|
||||
)
|
||||
else:
|
||||
auth_data = SimpleNamespace(
|
||||
id=UUID(payload["user_id"]), tenant_id=None, roles=payload["roles"]
|
||||
)
|
||||
# async def get_authenticated_user(
|
||||
# creds: HTTPAuthorizationCredentials = Security(bearer_scheme),
|
||||
# ) -> SimpleNamespace:
|
||||
# """
|
||||
# Extract and validate the JWT presented in the Authorization header.
|
||||
# """
|
||||
# if creds is None: # header missing
|
||||
# raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
|
||||
return auth_data
|
||||
# if creds.scheme.lower() != "bearer": # shouldn't happen extra guard
|
||||
# raise HTTPException(status_code=401, detail="Invalid authentication scheme")
|
||||
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise HTTPException(status_code=401, detail="Token has expired")
|
||||
except jwt.InvalidTokenError:
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
# token = creds.credentials
|
||||
# try:
|
||||
# payload = jwt.decode(
|
||||
# token, os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret"), algorithms=["HS256"]
|
||||
# )
|
||||
|
||||
# auth_data = SimpleNamespace(id=UUID(payload["user_id"]))
|
||||
# return auth_data
|
||||
|
||||
# except jwt.ExpiredSignatureError:
|
||||
# raise HTTPException(status_code=401, detail="Token has expired")
|
||||
# except jwt.InvalidTokenError:
|
||||
# raise HTTPException(status_code=401, detail="Invalid token")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from types import SimpleNamespace
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.exc import NoResultFound
|
||||
from sqlalchemy.future import select
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.base_config import get_base_config
|
||||
|
|
@ -33,5 +34,6 @@ async def get_default_user() -> SimpleNamespace:
|
|||
except Exception as error:
|
||||
if "principals" in str(error.args):
|
||||
raise DatabaseNotCreatedError() from error
|
||||
|
||||
raise UserNotFoundError(f"Failed to retrieve default user: {default_email}") from error
|
||||
if isinstance(error, NoResultFound):
|
||||
raise UserNotFoundError(f"Failed to retrieve default user: {default_email}") from error
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
from uuid import UUID
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import joinedload
|
||||
import sqlalchemy.exc
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from cognee.infrastructure.databases.exceptions import EntityNotFoundError
|
||||
from ..models import User
|
||||
|
||||
|
||||
|
|
@ -17,4 +19,7 @@ async def get_user(user_id: UUID):
|
|||
)
|
||||
).scalar()
|
||||
|
||||
if not user:
|
||||
raise EntityNotFoundError(message=f"Could not find user: {user_id}")
|
||||
|
||||
return user
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ class ACL(Base):
|
|||
|
||||
principal_id = Column(UUID, ForeignKey("principals.id"))
|
||||
permission_id = Column(UUID, ForeignKey("permissions.id"))
|
||||
data_id = Column(UUID, ForeignKey("data.id", ondelete="CASCADE"))
|
||||
dataset_id = Column(UUID, ForeignKey("datasets.id", ondelete="CASCADE"))
|
||||
|
||||
principal = relationship("Principal")
|
||||
permission = relationship("Permission")
|
||||
data = relationship("Data", back_populates="acls")
|
||||
dataset = relationship("Dataset", back_populates="acls")
|
||||
|
|
|
|||
19
cognee/modules/users/models/DatasetDatabase.py
Normal file
19
cognee/modules/users/models/DatasetDatabase.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import Column, DateTime, String, UUID, ForeignKey
|
||||
from cognee.infrastructure.databases.relational import Base
|
||||
|
||||
|
||||
class DatasetDatabase(Base):
|
||||
__tablename__ = "dataset_database"
|
||||
|
||||
owner_id = Column(UUID, ForeignKey("principals.id", ondelete="CASCADE"), index=True)
|
||||
dataset_id = Column(
|
||||
UUID, ForeignKey("datasets.id", ondelete="CASCADE"), primary_key=True, index=True
|
||||
)
|
||||
|
||||
vector_database_name = Column(String, unique=True, nullable=False)
|
||||
graph_database_name = Column(String, unique=True, nullable=False)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc))
|
||||
|
|
@ -11,6 +11,8 @@ class Tenant(Principal):
|
|||
id = Column(UUID, ForeignKey("principals.id"), primary_key=True)
|
||||
name = Column(String, unique=True, nullable=False, index=True)
|
||||
|
||||
owner_id = Column(UUID, index=True)
|
||||
|
||||
# One-to-Many relationship with User; specify the join via User.tenant_id
|
||||
users = relationship(
|
||||
"User",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from .User import User
|
||||
from .Role import Role
|
||||
from .UserRole import UserRole
|
||||
from .DatasetDatabase import DatasetDatabase
|
||||
from .RoleDefaultPermissions import RoleDefaultPermissions
|
||||
from .UserDefaultPermissions import UserDefaultPermissions
|
||||
from .TenantDefaultPermissions import TenantDefaultPermissions
|
||||
|
|
|
|||
1
cognee/modules/users/permissions/__init__.py
Normal file
1
cognee/modules/users/permissions/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .permission_types import PERMISSION_TYPES
|
||||
|
|
@ -1,6 +1,13 @@
|
|||
from .check_permission_on_documents import check_permission_on_documents
|
||||
from .give_permission_on_document import give_permission_on_document
|
||||
from .get_role import get_role
|
||||
from .get_tenant import get_tenant
|
||||
from .get_principal import get_principal
|
||||
from .get_principal_datasets import get_principal_datasets
|
||||
from .get_all_user_permission_datasets import get_all_user_permission_datasets
|
||||
from .get_specific_user_permission_datasets import get_specific_user_permission_datasets
|
||||
from .check_permission_on_dataset import check_permission_on_dataset
|
||||
from .give_permission_on_dataset import give_permission_on_dataset
|
||||
from .get_document_ids_for_user import get_document_ids_for_user
|
||||
from .authorized_give_permission_on_datasets import authorized_give_permission_on_datasets
|
||||
from .give_default_permission_to_tenant import give_default_permission_to_tenant
|
||||
from .give_default_permission_to_role import give_default_permission_to_role
|
||||
from .give_default_permission_to_user import give_default_permission_to_user
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
from typing import Union, List
|
||||
|
||||
from cognee.modules.users.permissions.methods import get_principal
|
||||
from cognee.modules.users.permissions.methods import give_permission_on_dataset
|
||||
from cognee.modules.users.permissions.methods import get_specific_user_permission_datasets
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
async def authorized_give_permission_on_datasets(
|
||||
principal_id: UUID, dataset_ids: Union[List[UUID], UUID], permission_name: str, owner_id: UUID
|
||||
):
|
||||
# If only a single dataset UUID is provided transform it to a list
|
||||
if not isinstance(dataset_ids, list):
|
||||
dataset_ids = [dataset_ids]
|
||||
|
||||
principal = await get_principal(principal_id)
|
||||
|
||||
# Check if request owner has permission to share dataset access
|
||||
datasets = await get_specific_user_permission_datasets(owner_id, "share", dataset_ids)
|
||||
|
||||
# TODO: Do we want to enforce sharing of datasets to only be between users of the same tenant?
|
||||
for dataset in datasets:
|
||||
await give_permission_on_dataset(principal, dataset.id, permission_name)
|
||||
|
|
@ -13,29 +13,29 @@ from ...models.ACL import ACL
|
|||
logger = get_logger()
|
||||
|
||||
|
||||
async def check_permission_on_documents(user: User, permission_type: str, document_ids: list[UUID]):
|
||||
async def check_permission_on_dataset(user: User, permission_type: str, dataset_id: UUID):
|
||||
if user is None:
|
||||
user = await get_default_user()
|
||||
|
||||
# TODO: Enable user role permissions again. Temporarily disabled during rework.
|
||||
# # TODO: Enable user role permissions again. Temporarily disabled during rework.
|
||||
# user_roles_ids = [role.id for role in user.roles]
|
||||
user_roles_ids = []
|
||||
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
# If dataset id was returned it means the user has permission to access it
|
||||
result = await session.execute(
|
||||
select(ACL)
|
||||
.join(ACL.permission)
|
||||
.options(joinedload(ACL.data))
|
||||
.options(joinedload(ACL.dataset))
|
||||
.where(ACL.principal_id.in_([user.id, *user_roles_ids]))
|
||||
.where(ACL.permission.has(name=permission_type))
|
||||
)
|
||||
acls = result.unique().scalars().all()
|
||||
data_ids = [acl.data.id for acl in acls]
|
||||
has_permissions = all(document_id in data_ids for document_id in document_ids)
|
||||
has_permission = dataset_id in [acl.dataset.id for acl in acls]
|
||||
|
||||
if not has_permissions:
|
||||
if not has_permission:
|
||||
raise PermissionDeniedError(
|
||||
message=f"User {user.id} does not have {permission_type} permission on documents"
|
||||
)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
from cognee.shared.logging_utils import get_logger
|
||||
|
||||
from ...models.User import User
|
||||
from cognee.modules.data.models.Dataset import Dataset
|
||||
from cognee.modules.users.permissions.methods import get_principal_datasets
|
||||
from cognee.modules.users.permissions.methods import get_role, get_tenant
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
async def get_all_user_permission_datasets(user: User, permission_type: str) -> list[Dataset]:
|
||||
datasets = list()
|
||||
# Get all datasets User has explicit access to
|
||||
datasets.extend(await get_principal_datasets(user, permission_type))
|
||||
|
||||
if user.tenant_id:
|
||||
# Get all datasets all tenants have access to
|
||||
tenant = await get_tenant(user.tenant_id)
|
||||
datasets.extend(await get_principal_datasets(tenant, permission_type))
|
||||
# Get all datasets Users roles have access to
|
||||
for role_name in user.roles:
|
||||
role = await get_role(user.tenant_id, role_name)
|
||||
datasets.extend(await get_principal_datasets(role, permission_type))
|
||||
|
||||
# Deduplicate datasets with same ID
|
||||
unique = {}
|
||||
for dataset in datasets:
|
||||
# If the dataset id key already exists, leave the dictionary unchanged.
|
||||
unique.setdefault(dataset.id, dataset)
|
||||
|
||||
return list(unique.values())
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
from uuid import UUID
|
||||
|
||||
from cognee.modules.data.methods import get_dataset_data
|
||||
from sqlalchemy import select
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from cognee.modules.data.models import Dataset, DatasetData, Data
|
||||
from cognee.modules.data.models import Dataset, DatasetData
|
||||
from ...models import ACL, Permission
|
||||
|
||||
|
||||
|
|
@ -10,10 +12,10 @@ async def get_document_ids_for_user(user_id: UUID, datasets: list[str] = None) -
|
|||
|
||||
async with db_engine.get_async_session() as session:
|
||||
async with session.begin():
|
||||
document_ids = (
|
||||
dataset_ids = (
|
||||
await session.scalars(
|
||||
select(Data.id)
|
||||
.join(ACL.data)
|
||||
select(Dataset.id)
|
||||
.join(ACL.dataset)
|
||||
.join(ACL.permission)
|
||||
.where(
|
||||
ACL.principal_id == user_id,
|
||||
|
|
@ -22,9 +24,15 @@ async def get_document_ids_for_user(user_id: UUID, datasets: list[str] = None) -
|
|||
)
|
||||
).all()
|
||||
|
||||
# Get documents from datasets user has read access for
|
||||
document_ids = []
|
||||
for dataset_id in dataset_ids:
|
||||
data_list = await get_dataset_data(dataset_id)
|
||||
document_ids.extend([data.id for data in data_list])
|
||||
|
||||
if datasets:
|
||||
documents_ids_in_dataset = set()
|
||||
# If datasets are specified filter out documents that aren't part of the specified datasets
|
||||
documents_ids_in_dataset = set()
|
||||
for dataset in datasets:
|
||||
# Find dataset id for dataset element
|
||||
dataset_id = (
|
||||
|
|
|
|||
14
cognee/modules/users/permissions/methods/get_principal.py
Normal file
14
cognee/modules/users/permissions/methods/get_principal.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from sqlalchemy import select
|
||||
from uuid import UUID
|
||||
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from ...models.Principal import Principal
|
||||
|
||||
|
||||
async def get_principal(principal_id: UUID):
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
result = await session.execute(select(Principal).where(Principal.id == principal_id))
|
||||
principal = result.unique().scalar_one()
|
||||
return principal
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
|
||||
from ...models.Principal import Principal
|
||||
from cognee.modules.data.models.Dataset import Dataset
|
||||
from ...models.ACL import ACL
|
||||
|
||||
|
||||
async def get_principal_datasets(principal: Principal, permission_type: str) -> list[Dataset]:
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
# If dataset id was returned it means the principal has permission to access it
|
||||
result = await session.execute(
|
||||
select(ACL)
|
||||
.join(ACL.permission)
|
||||
.options(joinedload(ACL.dataset))
|
||||
.where(ACL.principal_id == principal.id)
|
||||
.where(ACL.permission.has(name=permission_type))
|
||||
)
|
||||
acls = result.unique().scalars().all()
|
||||
return [acl.dataset for acl in acls]
|
||||
24
cognee/modules/users/permissions/methods/get_role.py
Normal file
24
cognee/modules/users/permissions/methods/get_role.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import sqlalchemy.exc
|
||||
from sqlalchemy import select
|
||||
from uuid import UUID
|
||||
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from cognee.modules.users.exceptions import RoleNotFoundError
|
||||
|
||||
from ...models.Role import Role
|
||||
|
||||
|
||||
async def get_role(tenant_id: UUID, role_name: str):
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
try:
|
||||
result = await session.execute(
|
||||
select(Role).where(Role.name == role_name).where(Role.tenant_id == tenant_id)
|
||||
)
|
||||
role = result.unique().scalar_one()
|
||||
if not role:
|
||||
raise RoleNotFoundError(message=f"Could not find {role_name} for given tenant")
|
||||
return role
|
||||
except sqlalchemy.exc.NoResultFound:
|
||||
raise RoleNotFoundError(message=f"Could not find {role_name} for given tenant")
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
from uuid import UUID
|
||||
from cognee.modules.data.models.Dataset import Dataset
|
||||
from cognee.modules.users.permissions.methods.get_all_user_permission_datasets import (
|
||||
get_all_user_permission_datasets,
|
||||
)
|
||||
from cognee.modules.users.exceptions import PermissionDeniedError
|
||||
from cognee.modules.users.methods import get_user
|
||||
|
||||
|
||||
async def get_specific_user_permission_datasets(
|
||||
user_id: UUID, permission_type: str, dataset_ids: list[UUID] = None
|
||||
) -> list[Dataset]:
|
||||
"""
|
||||
Return a list of datasets user has given permission for. If a list of datasets is provided,
|
||||
verify for which datasets user has appropriate permission for and return list of datasets he has permission for.
|
||||
Args:
|
||||
user_id:
|
||||
permission_type:
|
||||
dataset_ids:
|
||||
|
||||
Returns:
|
||||
list[Dataset]: List of datasets user has permission for
|
||||
"""
|
||||
user = await get_user(user_id)
|
||||
# Find all datasets user has permission for
|
||||
user_permission_access_datasets = await get_all_user_permission_datasets(user, permission_type)
|
||||
|
||||
# if specific datasets are provided filter out non provided datasets
|
||||
if dataset_ids:
|
||||
search_datasets = [
|
||||
dataset for dataset in user_permission_access_datasets if dataset.id in dataset_ids
|
||||
]
|
||||
# If there are requested datasets that user does not have access to raise error
|
||||
if len(search_datasets) != len(dataset_ids):
|
||||
raise PermissionDeniedError(
|
||||
f"Request owner does not have necessary permission: [{permission_type}] for all datasets requested."
|
||||
)
|
||||
else:
|
||||
search_datasets = user_permission_access_datasets
|
||||
|
||||
if len(search_datasets) == 0:
|
||||
raise PermissionDeniedError(
|
||||
f"Request owner does not have permission: [{permission_type}] for any dataset."
|
||||
)
|
||||
|
||||
return search_datasets
|
||||
21
cognee/modules/users/permissions/methods/get_tenant.py
Normal file
21
cognee/modules/users/permissions/methods/get_tenant.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import sqlalchemy.exc
|
||||
from sqlalchemy import select
|
||||
from uuid import UUID
|
||||
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from cognee.modules.users.exceptions import TenantNotFoundError
|
||||
from ...models.Tenant import Tenant
|
||||
|
||||
|
||||
async def get_tenant(tenant_id: UUID):
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
try:
|
||||
result = await session.execute(select(Tenant).where(Tenant.id == tenant_id))
|
||||
tenant = result.unique().scalar_one()
|
||||
if not tenant:
|
||||
raise TenantNotFoundError
|
||||
return tenant
|
||||
except sqlalchemy.exc.NoResultFound:
|
||||
raise TenantNotFoundError(message=f"Could not find tenant: {tenant_id}")
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
from sqlalchemy.future import select
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from ...models import Principal, ACL, Permission
|
||||
from uuid import UUID
|
||||
from cognee.modules.users.permissions import PERMISSION_TYPES
|
||||
from cognee.modules.users.exceptions import PermissionNotFoundError
|
||||
|
||||
|
||||
async def give_permission_on_dataset(
|
||||
principal: Principal,
|
||||
dataset_id: UUID,
|
||||
permission_name: str,
|
||||
):
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
permission = (
|
||||
(await session.execute(select(Permission).filter(Permission.name == permission_name)))
|
||||
.scalars()
|
||||
.first()
|
||||
)
|
||||
|
||||
if permission_name not in PERMISSION_TYPES:
|
||||
# If permission is not in allowed permission types
|
||||
raise PermissionNotFoundError(
|
||||
message=f"{permission_name} not found or not in allowed permission types"
|
||||
)
|
||||
elif permission is None:
|
||||
permission = Permission(name=permission_name)
|
||||
existing_acl = None
|
||||
else:
|
||||
# Check if the ACL entry already exists to avoid duplicates
|
||||
existing_acl = await session.execute(
|
||||
select(ACL).filter(
|
||||
ACL.principal_id == principal.id,
|
||||
ACL.dataset_id == dataset_id,
|
||||
ACL.permission_id == permission.id,
|
||||
)
|
||||
)
|
||||
existing_acl = existing_acl.scalars().first()
|
||||
|
||||
# If no existing ACL entry is found, proceed to add a new one
|
||||
if existing_acl is None:
|
||||
acl = ACL(principal_id=principal.id, dataset_id=dataset_id, permission=permission)
|
||||
session.add(acl)
|
||||
await session.commit()
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
from sqlalchemy.future import select
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from ...models import User, ACL, Permission
|
||||
|
||||
|
||||
async def give_permission_on_document(
|
||||
user: User,
|
||||
document_id: str,
|
||||
permission_name: str,
|
||||
):
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
permission = (
|
||||
(await session.execute(select(Permission).filter(Permission.name == permission_name)))
|
||||
.scalars()
|
||||
.first()
|
||||
)
|
||||
|
||||
if permission is None:
|
||||
permission = Permission(name=permission_name)
|
||||
|
||||
acl = ACL(principal_id=user.id, data_id=document_id, permission=permission)
|
||||
|
||||
session.add(acl)
|
||||
|
||||
await session.commit()
|
||||
1
cognee/modules/users/permissions/permission_types.py
Normal file
1
cognee/modules/users/permissions/permission_types.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
PERMISSION_TYPES = ["read", "write", "delete", "share"]
|
||||
|
|
@ -9,24 +9,40 @@ from cognee.infrastructure.databases.relational import get_relational_engine
|
|||
from cognee.modules.users.exceptions import (
|
||||
UserNotFoundError,
|
||||
RoleNotFoundError,
|
||||
TenantNotFoundError,
|
||||
PermissionDeniedError,
|
||||
)
|
||||
from cognee.modules.users.models import (
|
||||
User,
|
||||
Role,
|
||||
Tenant,
|
||||
UserRole,
|
||||
)
|
||||
|
||||
|
||||
async def add_user_to_role(user_id: UUID, role_id: UUID):
|
||||
async def add_user_to_role(user_id: UUID, role_id: UUID, owner_id: UUID):
|
||||
db_engine = get_relational_engine()
|
||||
async with db_engine.get_async_session() as session:
|
||||
user = (await session.execute(select(User).where(User.id == user_id))).scalars().first()
|
||||
role = (await session.execute(select(Role).where(Role.id == role_id))).scalars().first()
|
||||
tenant = (
|
||||
(await session.execute(select(Tenant).where(Tenant.id == role.tenant_id)))
|
||||
.scalars()
|
||||
.first()
|
||||
)
|
||||
|
||||
if not user:
|
||||
raise UserNotFoundError
|
||||
elif not role:
|
||||
raise RoleNotFoundError
|
||||
elif user.tenant_id != role.tenant_id:
|
||||
raise TenantNotFoundError(
|
||||
message="User tenant does not match role tenant. User cannot be added to role."
|
||||
)
|
||||
elif tenant.owner_id != owner_id:
|
||||
raise PermissionDeniedError(
|
||||
message="User submitting request does not have permission to add user to role."
|
||||
)
|
||||
|
||||
try:
|
||||
# Add association directly to the association table
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ from sqlalchemy.exc import IntegrityError
|
|||
|
||||
from cognee.infrastructure.databases.exceptions import EntityAlreadyExistsError
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from cognee.modules.users.methods import get_user
|
||||
from cognee.modules.users.permissions.methods import get_tenant
|
||||
from cognee.modules.users.exceptions import PermissionDeniedError
|
||||
from cognee.modules.users.models import (
|
||||
Role,
|
||||
)
|
||||
|
|
@ -11,13 +14,21 @@ from cognee.modules.users.models import (
|
|||
|
||||
async def create_role(
|
||||
role_name: str,
|
||||
tenant_id: UUID,
|
||||
owner_id: UUID,
|
||||
):
|
||||
db_engine = get_relational_engine()
|
||||
async with db_engine.get_async_session() as session:
|
||||
user = await get_user(owner_id)
|
||||
tenant = await get_tenant(user.tenant_id)
|
||||
|
||||
if owner_id != tenant.owner_id:
|
||||
raise PermissionDeniedError(
|
||||
"User submitting request does not have permission to create role for tenant."
|
||||
)
|
||||
|
||||
try:
|
||||
# Add association directly to the association table
|
||||
role = Role(name=role_name, tenant_id=tenant_id)
|
||||
role = Role(name=role_name, tenant_id=tenant.id)
|
||||
session.add(role)
|
||||
except IntegrityError:
|
||||
raise EntityAlreadyExistsError(message="Role already exists for tenant.")
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
from .create_tenant import create_tenant
|
||||
from .add_user_to_tenant import add_user_to_tenant
|
||||
|
|
|
|||
44
cognee/modules/users/tenants/methods/add_user_to_tenant.py
Normal file
44
cognee/modules/users/tenants/methods/add_user_to_tenant.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
from uuid import UUID
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from cognee.infrastructure.databases.exceptions import EntityAlreadyExistsError
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from cognee.modules.users.methods import get_user
|
||||
from cognee.modules.users.permissions.methods import get_tenant
|
||||
from cognee.modules.users.exceptions import (
|
||||
UserNotFoundError,
|
||||
TenantNotFoundError,
|
||||
PermissionDeniedError,
|
||||
)
|
||||
|
||||
|
||||
async def add_user_to_tenant(user_id: UUID, tenant_id: UUID, owner_id: UUID):
|
||||
db_engine = get_relational_engine()
|
||||
async with db_engine.get_async_session() as session:
|
||||
user = await get_user(user_id)
|
||||
tenant = await get_tenant(tenant_id)
|
||||
|
||||
if not user:
|
||||
raise UserNotFoundError
|
||||
elif not tenant:
|
||||
raise TenantNotFoundError
|
||||
|
||||
if tenant.owner_id != owner_id:
|
||||
raise PermissionDeniedError(
|
||||
message="Only tenant owner can add other users to organization."
|
||||
)
|
||||
|
||||
try:
|
||||
if user.tenant_id is None:
|
||||
user.tenant_id = tenant_id
|
||||
elif user.tenant_id == tenant_id:
|
||||
return
|
||||
else:
|
||||
raise IntegrityError
|
||||
|
||||
await session.merge(user)
|
||||
await session.commit()
|
||||
except IntegrityError:
|
||||
raise EntityAlreadyExistsError(
|
||||
message="User is already part of a tenant. Only one tenant can be assigned to user."
|
||||
)
|
||||
|
|
@ -1,19 +1,28 @@
|
|||
from uuid import UUID
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from cognee.infrastructure.databases.exceptions import EntityAlreadyExistsError
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
from cognee.modules.users.models import Tenant
|
||||
from cognee.modules.users.methods import get_user
|
||||
|
||||
|
||||
async def create_tenant(tenant_name: str):
|
||||
async def create_tenant(tenant_name: str, user_id: UUID):
|
||||
db_engine = get_relational_engine()
|
||||
async with db_engine.get_async_session() as session:
|
||||
try:
|
||||
# Add association directly to the association table
|
||||
tenant = Tenant(name=tenant_name)
|
||||
user = await get_user(user_id)
|
||||
if user.tenant_id:
|
||||
raise EntityAlreadyExistsError(
|
||||
message="User already has a tenant. New tenant cannot be created."
|
||||
)
|
||||
|
||||
tenant = Tenant(name=tenant_name, owner_id=user_id)
|
||||
session.add(tenant)
|
||||
await session.flush()
|
||||
|
||||
user.tenant_id = tenant.id
|
||||
await session.merge(user)
|
||||
await session.commit()
|
||||
except IntegrityError:
|
||||
raise EntityAlreadyExistsError(message="Tenant already exists.")
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(tenant)
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ from .translate_text import translate_text
|
|||
from .detect_language import detect_language
|
||||
from .classify_documents import classify_documents
|
||||
from .extract_chunks_from_documents import extract_chunks_from_documents
|
||||
from .check_permissions_on_documents import check_permissions_on_documents
|
||||
from .check_permissions_on_dataset import check_permissions_on_dataset
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from cognee.modules.data.processing.document_types import Document
|
||||
from cognee.modules.users.permissions.methods import check_permission_on_documents
|
||||
from cognee.modules.users.permissions.methods import check_permission_on_dataset
|
||||
from typing import List
|
||||
|
||||
|
||||
async def check_permissions_on_documents(
|
||||
documents: list[Document], user, permissions
|
||||
async def check_permissions_on_dataset(
|
||||
documents: List[Document], context: dict, user, permissions
|
||||
) -> List[Document]:
|
||||
"""
|
||||
Validates a user's permissions on a list of documents.
|
||||
|
|
@ -14,13 +14,12 @@ async def check_permissions_on_documents(
|
|||
- It is designed to validate multiple permissions in a sequential manner for the same set of documents.
|
||||
- Ensure that the `Document` and `user` objects conform to the expected structure and interfaces.
|
||||
"""
|
||||
document_ids = [document.id for document in documents]
|
||||
|
||||
for permission in permissions:
|
||||
await check_permission_on_documents(
|
||||
await check_permission_on_dataset(
|
||||
user,
|
||||
permission,
|
||||
document_ids,
|
||||
context["dataset"].id,
|
||||
)
|
||||
|
||||
return documents
|
||||
|
|
@ -2,6 +2,7 @@ import dlt
|
|||
import s3fs
|
||||
import json
|
||||
import inspect
|
||||
from uuid import UUID
|
||||
from typing import Union, BinaryIO, Any, List, Optional
|
||||
import cognee.modules.ingestion as ingestion
|
||||
from cognee.infrastructure.databases.relational import get_relational_engine
|
||||
|
|
@ -9,7 +10,8 @@ from cognee.modules.data.methods import create_dataset, get_dataset_data, get_da
|
|||
from cognee.modules.users.methods import get_default_user
|
||||
from cognee.modules.data.models.DatasetData import DatasetData
|
||||
from cognee.modules.users.models import User
|
||||
from cognee.modules.users.permissions.methods import give_permission_on_document
|
||||
from cognee.modules.users.permissions.methods import give_permission_on_dataset
|
||||
from cognee.modules.users.permissions.methods import get_specific_user_permission_datasets
|
||||
from .get_dlt_destination import get_dlt_destination
|
||||
from .save_data_item_to_storage import save_data_item_to_storage
|
||||
|
||||
|
|
@ -18,7 +20,11 @@ from cognee.api.v1.add.config import get_s3_config
|
|||
|
||||
|
||||
async def ingest_data(
|
||||
data: Any, dataset_name: str, user: User, node_set: Optional[List[str]] = None
|
||||
data: Any,
|
||||
dataset_name: str,
|
||||
user: User,
|
||||
node_set: Optional[List[str]] = None,
|
||||
dataset_id: UUID = None,
|
||||
):
|
||||
destination = get_dlt_destination()
|
||||
|
||||
|
|
@ -73,7 +79,11 @@ async def ingest_data(
|
|||
}
|
||||
|
||||
async def store_data_to_dataset(
|
||||
data: Any, dataset_name: str, user: User, node_set: Optional[List[str]] = None
|
||||
data: Any,
|
||||
dataset_name: str,
|
||||
user: User,
|
||||
node_set: Optional[List[str]] = None,
|
||||
dataset_id: UUID = None,
|
||||
):
|
||||
if not isinstance(data, list):
|
||||
# Convert data to a list as we work with lists further down.
|
||||
|
|
@ -104,7 +114,17 @@ async def ingest_data(
|
|||
db_engine = get_relational_engine()
|
||||
|
||||
async with db_engine.get_async_session() as session:
|
||||
dataset = await create_dataset(dataset_name, user, session)
|
||||
if dataset_id:
|
||||
# Retrieve existing dataset
|
||||
dataset = await get_specific_user_permission_datasets(
|
||||
user.id, "write", [dataset_id]
|
||||
)
|
||||
# Convert from list to Dataset element
|
||||
if isinstance(dataset, list):
|
||||
dataset = dataset[0]
|
||||
else:
|
||||
# Create new one
|
||||
dataset = await create_dataset(dataset_name, user, session)
|
||||
|
||||
# Check to see if data should be updated
|
||||
data_point = (
|
||||
|
|
@ -138,6 +158,7 @@ async def ingest_data(
|
|||
node_set=json.dumps(node_set) if node_set else None,
|
||||
token_count=-1,
|
||||
)
|
||||
session.add(data_point)
|
||||
|
||||
# Check if data is already in dataset
|
||||
dataset_data = (
|
||||
|
|
@ -150,17 +171,20 @@ async def ingest_data(
|
|||
# If data is not present in dataset add it
|
||||
if dataset_data is None:
|
||||
dataset.data.append(data_point)
|
||||
await session.merge(dataset)
|
||||
|
||||
await session.commit()
|
||||
|
||||
await give_permission_on_document(user, data_id, "read")
|
||||
await give_permission_on_document(user, data_id, "write")
|
||||
await give_permission_on_dataset(user, dataset.id, "read")
|
||||
await give_permission_on_dataset(user, dataset.id, "write")
|
||||
await give_permission_on_dataset(user, dataset.id, "delete")
|
||||
await give_permission_on_dataset(user, dataset.id, "share")
|
||||
|
||||
return file_paths
|
||||
|
||||
db_engine = get_relational_engine()
|
||||
|
||||
file_paths = await store_data_to_dataset(data, dataset_name, user, node_set)
|
||||
file_paths = await store_data_to_dataset(data, dataset_name, user, node_set, dataset_id)
|
||||
|
||||
# Note: DLT pipeline has its own event loop, therefore objects created in another event loop
|
||||
# can't be used inside the pipeline
|
||||
|
|
|
|||
71
cognee/tests/test_parallel_databases.py
Executable file
71
cognee/tests/test_parallel_databases.py
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
import os
|
||||
import pathlib
|
||||
import cognee
|
||||
from cognee.modules.search.operations import get_history
|
||||
from cognee.modules.users.methods import get_default_user
|
||||
from cognee.shared.logging_utils import get_logger
|
||||
from cognee.modules.search.types import SearchType
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
async def main():
|
||||
data_directory_path = str(
|
||||
pathlib.Path(
|
||||
os.path.join(pathlib.Path(__file__).parent, ".data_storage/test_library")
|
||||
).resolve()
|
||||
)
|
||||
cognee.config.data_root_directory(data_directory_path)
|
||||
cognee_directory_path = str(
|
||||
pathlib.Path(
|
||||
os.path.join(pathlib.Path(__file__).parent, ".cognee_system/test_library")
|
||||
).resolve()
|
||||
)
|
||||
cognee.config.system_root_directory(cognee_directory_path)
|
||||
|
||||
await cognee.prune.prune_data()
|
||||
await cognee.prune.prune_system(metadata=True)
|
||||
|
||||
await cognee.add(["TEST1"], "test1")
|
||||
await cognee.add(["TEST2"], "test2")
|
||||
|
||||
task_1_config = {
|
||||
"vector_db_url": "cognee1.test",
|
||||
"vector_db_key": "",
|
||||
"vector_db_provider": "lancedb",
|
||||
}
|
||||
task_2_config = {
|
||||
"vector_db_url": "cognee2.test",
|
||||
"vector_db_key": "",
|
||||
"vector_db_provider": "lancedb",
|
||||
}
|
||||
|
||||
task_1_graph_config = {
|
||||
"graph_database_provider": "kuzu",
|
||||
"graph_file_path": "kuzu1.db",
|
||||
}
|
||||
task_2_graph_config = {
|
||||
"graph_database_provider": "kuzu",
|
||||
"graph_file_path": "kuzu2.db",
|
||||
}
|
||||
|
||||
# schedule both cognify calls concurrently
|
||||
task1 = asyncio.create_task(
|
||||
cognee.cognify(
|
||||
["test1"], vector_db_config=task_1_config, graph_db_config=task_1_graph_config
|
||||
)
|
||||
)
|
||||
task2 = asyncio.create_task(
|
||||
cognee.cognify(
|
||||
["test2"], vector_db_config=task_2_config, graph_db_config=task_2_graph_config
|
||||
)
|
||||
)
|
||||
|
||||
# wait until both are done (raises first error if any)
|
||||
await asyncio.gather(task1, task2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
asyncio.run(main(), debug=True)
|
||||
|
|
@ -144,7 +144,6 @@ async def main():
|
|||
graph_completion = await cognee.search(
|
||||
query_type=SearchType.GRAPH_COMPLETION,
|
||||
query_text=random_node_name,
|
||||
datasets=[dataset_name_2],
|
||||
)
|
||||
assert len(graph_completion) != 0, "Completion result is empty."
|
||||
print("Completion result is:")
|
||||
|
|
|
|||
|
|
@ -24,13 +24,9 @@ def mock_user():
|
|||
@pytest.mark.asyncio
|
||||
@patch.object(search_module, "log_query")
|
||||
@patch.object(search_module, "log_result")
|
||||
@patch.object(search_module, "get_document_ids_for_user")
|
||||
@patch.object(search_module, "specific_search")
|
||||
@patch.object(search_module, "parse_id")
|
||||
async def test_search(
|
||||
mock_parse_id,
|
||||
mock_specific_search,
|
||||
mock_get_document_ids,
|
||||
mock_log_result,
|
||||
mock_log_query,
|
||||
mock_user,
|
||||
|
|
@ -49,7 +45,6 @@ async def test_search(
|
|||
doc_id1 = uuid.uuid4()
|
||||
doc_id2 = uuid.uuid4()
|
||||
doc_id3 = uuid.uuid4() # This one will be filtered out
|
||||
mock_get_document_ids.return_value = [doc_id1, doc_id2]
|
||||
|
||||
# Mock search results
|
||||
search_results = [
|
||||
|
|
@ -59,15 +54,11 @@ async def test_search(
|
|||
]
|
||||
mock_specific_search.return_value = search_results
|
||||
|
||||
# Mock parse_id to return the same UUID
|
||||
mock_parse_id.side_effect = lambda x: uuid.UUID(x) if x else None
|
||||
|
||||
# Execute
|
||||
results = await search(query_text, query_type, datasets, mock_user)
|
||||
|
||||
# Verify
|
||||
mock_log_query.assert_called_once_with(query_text, query_type.value, mock_user.id)
|
||||
mock_get_document_ids.assert_called_once_with(mock_user.id, datasets)
|
||||
mock_specific_search.assert_called_once_with(
|
||||
query_type,
|
||||
query_text,
|
||||
|
|
|
|||
131
poetry.lock
generated
131
poetry.lock
generated
|
|
@ -2046,6 +2046,25 @@ files = [
|
|||
{file = "duckdb-1.2.2.tar.gz", hash = "sha256:1e53555dece49201df08645dbfa4510c86440339889667702f936b7d28d39e43"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.19.1"
|
||||
description = "ECDSA cryptographic signature library (pure python)"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3"},
|
||||
{file = "ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.9.0"
|
||||
|
||||
[package.extras]
|
||||
gmpy = ["gmpy"]
|
||||
gmpy2 = ["gmpy2"]
|
||||
|
||||
[[package]]
|
||||
name = "email-validator"
|
||||
version = "2.2.0"
|
||||
|
|
@ -4454,50 +4473,50 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "kuzu"
|
||||
version = "0.8.2"
|
||||
version = "0.9.0"
|
||||
description = "Highly scalable, extremely fast, easy-to-use embeddable graph database"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"kuzu\""
|
||||
markers = "extra == \"api\" or extra == \"kuzu\""
|
||||
files = [
|
||||
{file = "kuzu-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:78bcdf6cc7b130bce8b307709e8d7bddd2e9104b2b696a9dc52574556e754570"},
|
||||
{file = "kuzu-0.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b42e3e9b1eacf830700287b05e96f9455b89dd4140085053e6c86b32c61e8d5c"},
|
||||
{file = "kuzu-0.8.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf06c602dc0231268d9cfa56a62afef15f8fca3be1ccd2cad22047a14bff4ae0"},
|
||||
{file = "kuzu-0.8.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50a873e7cd0c2e8e3093e9af14cffb14e49f1f67eceb32df3d0454ce101402d3"},
|
||||
{file = "kuzu-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4d36261444d31432606f3f3ed00624f1a3a8edcf7d830564c72b76ffbdf4d318"},
|
||||
{file = "kuzu-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c1694c6d1b19c46ad5d416cac429ccf1fe91aca4d367664e3aa0afa59800f93"},
|
||||
{file = "kuzu-0.8.2-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:00156c64523a1377ffced998bdb031709336f90543da69544c0ab4b40d533692"},
|
||||
{file = "kuzu-0.8.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc75f26afe8815b046cfb0d931303da6c36ce3afb49d4ae18a3899f23e62020f"},
|
||||
{file = "kuzu-0.8.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f0de6910724a74cc492354e903cf76db78b6353eef1e2edfa0b79d600c3c572"},
|
||||
{file = "kuzu-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:56e99c39a725943aa7ad96ada8f29706da3d53cc98385f2c663b8ea026f0dce3"},
|
||||
{file = "kuzu-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adcc250b34963a6eea62b59d47a091018d83e61fb2e95552795ab61f103052be"},
|
||||
{file = "kuzu-0.8.2-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:f72036924466143675980baed02a26c0fca15b6254c11de9a9c18d28fe66247e"},
|
||||
{file = "kuzu-0.8.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2fd7895fdfd9df880091d32bfb79c148f849659c67e2b9e185f952a6bde9139"},
|
||||
{file = "kuzu-0.8.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68486e291aa8a61264be7e31233ec34eeb6da2402f4b980c3f2b67f9ccbbea3a"},
|
||||
{file = "kuzu-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:7cce7d06e6f09cd488c62be7cafe78752b037ed9e6585ed3da9df029104b1987"},
|
||||
{file = "kuzu-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa0495f856f2e5f5067e281dab3fbc170aba0721d1f56156a8cd9fa50e706f91"},
|
||||
{file = "kuzu-0.8.2-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:823577b472ba63c3b36e5ff81e2b744736f9eaf0b71585c247f3defc9d268f53"},
|
||||
{file = "kuzu-0.8.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bde76f38d293f49ad283a4831bd32d41f185b93a75d388d67f9b8996678203e9"},
|
||||
{file = "kuzu-0.8.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cdb189012613ecd26630096796e3817c260deea85782e764309cd36b2c39dac5"},
|
||||
{file = "kuzu-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:71fb98721f9c46f960a5c3baea6b083026485c4b9a3e74ab01418243e29e3753"},
|
||||
{file = "kuzu-0.8.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e12726af2cb552ab7b60e2b4312469359bb3b4b45ddbcfb75220def4be6f566"},
|
||||
{file = "kuzu-0.8.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055f2cd9741bf39161f9ccff80428f8fb80b1910b2450b05bbe848487ba694f5"},
|
||||
{file = "kuzu-0.8.2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:18cb3da3a650f8dfde3639fbd6319a5ad6f98f60689c5dd96d20d8d1fc184d4c"},
|
||||
{file = "kuzu-0.8.2-cp37-cp37m-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e55a8fddc21ac3e27b3cf2815d93264dd3c89e9ad8c7f3960d51bdfe48a02709"},
|
||||
{file = "kuzu-0.8.2-cp37-cp37m-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d93600aceacdd7903aa39f016cb641811f96e4825b027a135aaaa1d82e23d24"},
|
||||
{file = "kuzu-0.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:68601d9e741c7815c3d3f46a9c6884853388bcc6920945f069d5dc4f9492c9c5"},
|
||||
{file = "kuzu-0.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32d7ff56d793df27f76129b8b15bd85c940e59bcb67acd189b6a5ed1af5e8b44"},
|
||||
{file = "kuzu-0.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5e639f24be2fca78bf3890774f273aa1a6b149bfdbeb5c7e966e03b8f610be98"},
|
||||
{file = "kuzu-0.8.2-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1caf46e2721dabed94b65cdcf3990551af2f3913c3f2dcd39f3e5397f0134243"},
|
||||
{file = "kuzu-0.8.2-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5333c9e4557ccbfef7b822793ec382848411c8d11fdee063064b41bd1828404"},
|
||||
{file = "kuzu-0.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:765a8bd4c5b9d24583eb8aaa20ecd753d78220138a82bf643ec592ffb8128298"},
|
||||
{file = "kuzu-0.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a215ff235d17a41c50d1cf2bd8e67a196eff32f23e59d989b1a40e6192f2008"},
|
||||
{file = "kuzu-0.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:074b5440186e4214b653d46f8d5a15d4b4cae1185d4656eaf598fe9b840fcdca"},
|
||||
{file = "kuzu-0.8.2-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32303a9533674a35e52d429f1446a82e2fc97c423618bc86aaafef1d4d2621e4"},
|
||||
{file = "kuzu-0.8.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0baea115bc55c8ed710f2beae8f02e46cf2bac42326b4e2c3acd25a76031f59d"},
|
||||
{file = "kuzu-0.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:70e031131c5b8e327edd63993b05fb04196b74d0ade1baf0f4005968610310ed"},
|
||||
{file = "kuzu-0.8.2.tar.gz", hash = "sha256:68ad72b3ef6a32a41ecfa955fa4ca9ca0c8a36d3a1bc13e34cc70c971b2b8ca7"},
|
||||
{file = "kuzu-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec9f216d67c092ea52086c99cf4b1deabe0f8daaf47c80cf1892b3b41c57d58a"},
|
||||
{file = "kuzu-0.9.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:bda6d845bf1c7da204ffa7730573118f2d43fe6b14b1a5d0d2845ec3d3481362"},
|
||||
{file = "kuzu-0.9.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab5b28f101c93899fc15668b6cb25f6db3d4a9844fcc4affed293caaaafaa4b7"},
|
||||
{file = "kuzu-0.9.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183bb1de19ffec1c3b07c0b4d5eecf02eb4eeafc1d50aea409bc91e1fad4d6d2"},
|
||||
{file = "kuzu-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:2e36ce7da1bbebb538082656de18a717895d9352a33c8bcac170ef2fc22a4902"},
|
||||
{file = "kuzu-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82dd690d823df816e7826945e5243a4ae65e3e948ef512709a59205b84b9f6dd"},
|
||||
{file = "kuzu-0.9.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:c394e019a14e9c5636228cf1acd333997c31e5da3d9a60a1df2c03b828438432"},
|
||||
{file = "kuzu-0.9.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7d493f88ed31eada4b88a92b115bc6085c60498c47336ab06a489e75a727bab"},
|
||||
{file = "kuzu-0.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:171b47cf2b3923c813f1ed88fb9d3964a9355129b5d3ebca54eba3450bfc1f97"},
|
||||
{file = "kuzu-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:3c8a8a611f599801c8db6aeffb978cd1badcfa3ec8f79c15b701810fee71765f"},
|
||||
{file = "kuzu-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:509af4029f9dcb9c3e843a825df44ec30009a70fad891cbcfb611c3b8cdfefd6"},
|
||||
{file = "kuzu-0.9.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:885f17f6e46c15ecef121fc57a941f8b60f0a5c1d3995813bb7a4c7437fb2259"},
|
||||
{file = "kuzu-0.9.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f2e35aa345b543a4a21de0e82b70eac4c753987cfa4ded75ae7f9f23edbf11"},
|
||||
{file = "kuzu-0.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:67430c9813607a3b901c4a1e6bfb3b93538af230bc821e675c552a162818f589"},
|
||||
{file = "kuzu-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:549f4a72f815554fb998582876c5875cb0917a192e6a58d196e8247fd8902701"},
|
||||
{file = "kuzu-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ec2e709599b4015d0a179a191dd7850e7bf076f83b37b70d0dc2e4ee59ce7725"},
|
||||
{file = "kuzu-0.9.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:8aad4fbd74b283ffb0b115138dfc62d9775c8f19ba62ab243e55e3cd648652b6"},
|
||||
{file = "kuzu-0.9.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba9dd4f412e31d34345b6461fc9489955ae9566abf426e56af478b6e791b735a"},
|
||||
{file = "kuzu-0.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340502cbce54f21a5b2440a75c28d61ddfd26d6d6848e9daa6140798bdd5b367"},
|
||||
{file = "kuzu-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:e1ddb189dfa2aee0123dcd1a5ccc5b831a7f297233a09fccfd76294fc2f9e6bd"},
|
||||
{file = "kuzu-0.9.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fae68db87ba48268228c89e70ed1fde2f43843d8ed6b2debaafd314c45e8542"},
|
||||
{file = "kuzu-0.9.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0279ba37c639d96f303eb6ad4481e634495be31210991d8008c385ee50b4e0a"},
|
||||
{file = "kuzu-0.9.0-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:3ca7424fe3831df687552b89903aa57fb88efff9c25df15c5d678fae7c933199"},
|
||||
{file = "kuzu-0.9.0-cp37-cp37m-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bce9284913434661f47cecfc763f8997a61ebd2bb7bfe993970c1403924708fa"},
|
||||
{file = "kuzu-0.9.0-cp37-cp37m-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66040cdf9a59a5423b49c3d2bc01a089114b573ee1345d5a7c912276fbca0135"},
|
||||
{file = "kuzu-0.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8e195774364123845df071eddb18873ce8c78244dd6f854badfe65053b058088"},
|
||||
{file = "kuzu-0.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2906f29ee36f9f642bdb8f5222c94f667092e38bde7dc53ebb252f9eb524ab6a"},
|
||||
{file = "kuzu-0.9.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:4c3218e266766080fe1b31325d0156d1b334f62ae23dac854c3e4919115ef8c6"},
|
||||
{file = "kuzu-0.9.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a26214c1600c21f5e4aa96585706953a8792ad77e14788710d78f8af0d6b74ec"},
|
||||
{file = "kuzu-0.9.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b153fb28db9336757346eabb24b8c179b4ed48578a0ef158210fbc935df2184"},
|
||||
{file = "kuzu-0.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:b6ee075e2571b11a434efb004cb0b3a2fbd7aa416ae680816869f1388e5fc734"},
|
||||
{file = "kuzu-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:56874ae750ff99b15c959d884b175adf24ac912ab08e084c42784902b2bce2fb"},
|
||||
{file = "kuzu-0.9.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:6e0265b1ad445500397dc0df3cc4e7faddfd67fcd3d0952d9a4cdab6b77b47e9"},
|
||||
{file = "kuzu-0.9.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d66e69a3e135ea123cc7c9c2e507bbb614ffdbfe7be835782c6a588ae63ff900"},
|
||||
{file = "kuzu-0.9.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e11c8b7186798ad95563e1d7ebf84495d817c406bd28c21af7170467e37e35e"},
|
||||
{file = "kuzu-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:4fb80eb6c71b02c4e57e3570b079c494082f7ff819d4c06ac482914f29211294"},
|
||||
{file = "kuzu-0.9.0.tar.gz", hash = "sha256:2e59f3d4d1fc385e9e90d7ae09f072ec2f4cfeff508582523a0034ceb076f6eb"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -7841,10 +7860,9 @@ test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"]
|
|||
name = "pyasn1"
|
||||
version = "0.6.1"
|
||||
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"gemini\""
|
||||
files = [
|
||||
{file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
|
||||
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
|
||||
|
|
@ -8512,6 +8530,30 @@ files = [
|
|||
[package.extras]
|
||||
dev = ["black (==25.1.0)", "build (==1.2.2)", "flake8 (==7.1.1)", "mypy (==1.15.0)", "pytest (==8.3.4)", "requests (==2.32.3)", "twine (==6.1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-jose"
|
||||
version = "3.5.0"
|
||||
description = "JOSE implementation in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771"},
|
||||
{file = "python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"cryptography\""}
|
||||
ecdsa = "!=0.15"
|
||||
pyasn1 = ">=0.5.0"
|
||||
rsa = ">=4.0,<4.1.1 || >4.1.1,<4.4 || >4.4,<5.0"
|
||||
|
||||
[package.extras]
|
||||
cryptography = ["cryptography (>=3.4.0)"]
|
||||
pycrypto = ["pycrypto (>=2.6.0,<2.7.0)"]
|
||||
pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)"]
|
||||
test = ["pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "python-json-logger"
|
||||
version = "3.3.0"
|
||||
|
|
@ -9467,10 +9509,9 @@ files = [
|
|||
name = "rsa"
|
||||
version = "4.9.1"
|
||||
description = "Pure-Python RSA implementation"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = "<4,>=3.6"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"gemini\""
|
||||
files = [
|
||||
{file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"},
|
||||
{file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"},
|
||||
|
|
@ -11962,7 +12003,7 @@ cffi = ["cffi (>=1.11)"]
|
|||
|
||||
[extras]
|
||||
anthropic = ["anthropic"]
|
||||
api = ["gunicorn", "uvicorn"]
|
||||
api = ["gunicorn", "kuzu", "uvicorn"]
|
||||
chromadb = ["chromadb", "pypika"]
|
||||
codegraph = ["fastembed", "transformers", "tree-sitter", "tree-sitter-python"]
|
||||
debug = ["debugpy"]
|
||||
|
|
@ -11992,4 +12033,4 @@ weaviate = ["weaviate-client"]
|
|||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<=3.13"
|
||||
content-hash = "15b319ff8dbe5bd88e41ead93f4e9140b2b7d86d57a707682dd3a308e78ef245"
|
||||
content-hash = "b1a1a1525baa6b0e780a6938b714c9d7005b157736ed910eeb13211dd9b61de0"
|
||||
|
|
|
|||
|
|
@ -58,12 +58,14 @@ dependencies = [
|
|||
"structlog>=25.2.0,<26",
|
||||
"onnxruntime<=1.21.1",
|
||||
"pylance==0.22.0",
|
||||
"python-jose[cryptography]>=3.5.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
api = [
|
||||
"uvicorn==0.34.0",
|
||||
"gunicorn>=20.1.0,<21",
|
||||
"kuzu==0.9.0",
|
||||
]
|
||||
weaviate = ["weaviate-client==4.9.6"]
|
||||
qdrant = ["qdrant-client>=1.9.0,<2"]
|
||||
|
|
@ -87,7 +89,7 @@ anthropic = ["anthropic>=0.26.1,<0.27"]
|
|||
deepeval = ["deepeval>=2.0.1,<3"]
|
||||
posthog = ["posthog>=3.5.0,<4"]
|
||||
falkordb = ["falkordb==1.0.9"]
|
||||
kuzu = ["kuzu==0.8.2"]
|
||||
kuzu = ["kuzu==0.9.0"]
|
||||
groq = ["groq==0.8.0"]
|
||||
milvus = ["pymilvus>=2.5.0,<3"]
|
||||
chromadb = [
|
||||
|
|
|
|||
85
uv.lock
generated
85
uv.lock
generated
|
|
@ -892,6 +892,7 @@ dependencies = [
|
|||
{ name = "pylance" },
|
||||
{ name = "pypdf" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-jose", extra = ["cryptography"] },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "s3fs", extra = ["boto3"] },
|
||||
{ name = "scikit-learn" },
|
||||
|
|
@ -908,6 +909,7 @@ anthropic = [
|
|||
]
|
||||
api = [
|
||||
{ name = "gunicorn" },
|
||||
{ name = "kuzu" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
chromadb = [
|
||||
|
|
@ -1036,7 +1038,8 @@ requires-dist = [
|
|||
{ name = "gunicorn", marker = "extra == 'api'", specifier = ">=20.1.0,<21" },
|
||||
{ name = "instructor", specifier = "==1.7.2" },
|
||||
{ name = "jinja2", specifier = ">=3.1.3,<4" },
|
||||
{ name = "kuzu", marker = "extra == 'kuzu'", specifier = "==0.8.2" },
|
||||
{ name = "kuzu", marker = "extra == 'api'", specifier = "==0.9.0" },
|
||||
{ name = "kuzu", marker = "extra == 'kuzu'", specifier = "==0.9.0" },
|
||||
{ name = "lancedb", specifier = "==0.21.0" },
|
||||
{ name = "langchain-text-splitters", marker = "extra == 'langchain'", specifier = "==0.3.2" },
|
||||
{ name = "langfuse", specifier = ">=2.32.0" },
|
||||
|
|
@ -1077,6 +1080,7 @@ requires-dist = [
|
|||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.1,<0.22" },
|
||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.1.1" },
|
||||
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
||||
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.5.0" },
|
||||
{ name = "python-multipart", specifier = "==0.0.20" },
|
||||
{ name = "qasync", marker = "extra == 'gui'", specifier = ">=0.27.1,<0.28" },
|
||||
{ name = "qdrant-client", marker = "extra == 'qdrant'", specifier = ">=1.9.0,<2" },
|
||||
|
|
@ -1698,6 +1702,18 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/3f/3d/ce68db53084746a4a62695a4cb064e44ce04123f8582bb3afbf6ee944e16/duckdb-1.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1aec7102670e59d83512cf47d32a6c77a79df9df0294c5e4d16b6259851e2e9", size = 11370206, upload-time = "2025-04-08T08:46:33.472Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "email-validator"
|
||||
version = "2.2.0"
|
||||
|
|
@ -3330,32 +3346,32 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "kuzu"
|
||||
version = "0.8.2"
|
||||
version = "0.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/20/db6604dc5cb2bc286623d9949b5817dba231191d82c5f0106ee1a55e5f10/kuzu-0.8.2.tar.gz", hash = "sha256:68ad72b3ef6a32a41ecfa955fa4ca9ca0c8a36d3a1bc13e34cc70c971b2b8ca7", size = 4726180, upload-time = "2025-02-24T16:22:34.701Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/72/d7f009f3d46bccd853749a6a6845be0c1ecc3607d39e28862d104ce119f8/kuzu-0.9.0.tar.gz", hash = "sha256:2e59f3d4d1fc385e9e90d7ae09f072ec2f4cfeff508582523a0034ceb076f6eb", size = 4839346, upload-time = "2025-04-01T19:17:24.881Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/ac/9b598ba4bf52b5854d7b809d582f7379d7e13cdcbb864546ab077d92a407/kuzu-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:78bcdf6cc7b130bce8b307709e8d7bddd2e9104b2b696a9dc52574556e754570", size = 3683754, upload-time = "2025-02-24T16:21:04.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/da/49614b09033c65b721e5ec6291d9f3dc1c2e284cdd922826f154d5313bbc/kuzu-0.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b42e3e9b1eacf830700287b05e96f9455b89dd4140085053e6c86b32c61e8d5c", size = 4162501, upload-time = "2025-02-24T16:21:06.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/47/5b16c554dcc503fc6fc483af716c7ea62c0d1e2c6ac7ae3004af22d3f0b4/kuzu-0.8.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf06c602dc0231268d9cfa56a62afef15f8fca3be1ccd2cad22047a14bff4ae0", size = 6059116, upload-time = "2025-02-24T16:21:09.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/ff/ca1b276f956b911224f70645e8f7878e64a2770439d6d49cd1d8a23b1bf9/kuzu-0.8.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50a873e7cd0c2e8e3093e9af14cffb14e49f1f67eceb32df3d0454ce101402d3", size = 6840544, upload-time = "2025-02-24T16:21:12.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/c8/8856d1ac84162ae41a1f0aead1cce01789493b3b9dfbac18032c0a805740/kuzu-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4d36261444d31432606f3f3ed00624f1a3a8edcf7d830564c72b76ffbdf4d318", size = 4151918, upload-time = "2025-02-24T16:21:13.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/08/ebcde26bf768a08a08a2d9e5b7ddca2a9186fa0b53952155c8273934a8cc/kuzu-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c1694c6d1b19c46ad5d416cac429ccf1fe91aca4d367664e3aa0afa59800f93", size = 3685637, upload-time = "2025-02-24T16:21:16.648Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/07/cbf0fc1950ab01bf96d9f21ebdba3dea85139c8845ee6a922786a1718f5e/kuzu-0.8.2-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:00156c64523a1377ffced998bdb031709336f90543da69544c0ab4b40d533692", size = 4163429, upload-time = "2025-02-24T16:21:18.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/93/12640770b907def8dbb410c9736f873a700a1c2ff1d20772fd9b38fe8129/kuzu-0.8.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc75f26afe8815b046cfb0d931303da6c36ce3afb49d4ae18a3899f23e62020f", size = 6059678, upload-time = "2025-02-24T16:21:20.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/9f/6130bac975719ea88f55bcfdc63b87a5ebcc2b3de64a96ac87941942adbd/kuzu-0.8.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f0de6910724a74cc492354e903cf76db78b6353eef1e2edfa0b79d600c3c572", size = 6841900, upload-time = "2025-02-24T16:21:22.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/7d/33bed3c22388c884203b03754208437d06caa75cfa5ab100df91e5e8fe53/kuzu-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:56e99c39a725943aa7ad96ada8f29706da3d53cc98385f2c663b8ea026f0dce3", size = 4152856, upload-time = "2025-02-24T16:21:24.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/74/92f86e5f6b74013e098c9154bf6dc9d3772ed51799d49a92c18f4fea91c4/kuzu-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adcc250b34963a6eea62b59d47a091018d83e61fb2e95552795ab61f103052be", size = 3686303, upload-time = "2025-02-24T16:21:26.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/37/83e06ca9429ce736f42e798a066ea9ebf03d417f584e1afe30946387562e/kuzu-0.8.2-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:f72036924466143675980baed02a26c0fca15b6254c11de9a9c18d28fe66247e", size = 4165552, upload-time = "2025-02-24T16:21:28.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/46/2552eecb57630fd3d575bee8970f4cfbf088344f95677b759dd0add3495c/kuzu-0.8.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2fd7895fdfd9df880091d32bfb79c148f849659c67e2b9e185f952a6bde9139", size = 6060338, upload-time = "2025-02-24T16:21:31.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/47/e5c41235edf2761aeffb6621cc2e24e28a64d207cfd0a154dc67f772ac8c/kuzu-0.8.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68486e291aa8a61264be7e31233ec34eeb6da2402f4b980c3f2b67f9ccbbea3a", size = 6839327, upload-time = "2025-02-24T16:21:34.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/16/2db5b0e141801378b4f1a563c04172d0a90d48fd86cba1c72a586c9807a1/kuzu-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:7cce7d06e6f09cd488c62be7cafe78752b037ed9e6585ed3da9df029104b1987", size = 4153607, upload-time = "2025-02-24T16:21:36.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/63/b721083ffc4ef9104422a9d85c15f528961899b85ebed4daa7b7780783cb/kuzu-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa0495f856f2e5f5067e281dab3fbc170aba0721d1f56156a8cd9fa50e706f91", size = 3686406, upload-time = "2025-02-24T16:21:38.69Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/80/452f583f054baf700501f0578dfabe791118cbbfb5571e196e3ebe671e89/kuzu-0.8.2-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:823577b472ba63c3b36e5ff81e2b744736f9eaf0b71585c247f3defc9d268f53", size = 4165732, upload-time = "2025-02-24T16:21:40.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/c5/0cf153a33fe2da3c0c798744cbe4017990682aab4899900ff222bac74ba3/kuzu-0.8.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bde76f38d293f49ad283a4831bd32d41f185b93a75d388d67f9b8996678203e9", size = 6060261, upload-time = "2025-02-24T16:21:42.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/15/7fa963c11eb1147c68c6a1ca01c5ced8f1541f09b5a9687ed86fc8aaac08/kuzu-0.8.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cdb189012613ecd26630096796e3817c260deea85782e764309cd36b2c39dac5", size = 6838575, upload-time = "2025-02-24T16:21:44.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/3f/5553a6ea992f6f8ee5c5c446169bbcad27c92191eff43c44b5c76ce2b588/kuzu-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:71fb98721f9c46f960a5c3baea6b083026485c4b9a3e74ab01418243e29e3753", size = 4153578, upload-time = "2025-02-24T16:21:47.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/75/bd2af3282b1f0d168f008660a557726dc06fef54b037e169dcfe458ecaaf/kuzu-0.8.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e12726af2cb552ab7b60e2b4312469359bb3b4b45ddbcfb75220def4be6f566", size = 6065843, upload-time = "2025-02-24T16:21:49.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/d5/b2dd9c31fa76d48c778b6baf3df3de8d792d24688d15814783fb43854cee/kuzu-0.8.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055f2cd9741bf39161f9ccff80428f8fb80b1910b2450b05bbe848487ba694f5", size = 6843688, upload-time = "2025-02-24T16:21:52.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/a6/d97cc82ec808d707b9d89fb21cb62d418e74f5533b06d85a7ec9c1bdf658/kuzu-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec9f216d67c092ea52086c99cf4b1deabe0f8daaf47c80cf1892b3b41c57d58a", size = 3667262, upload-time = "2025-04-01T19:16:12.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/c4/97d07a16a437a21f9334f22d874d5491c77d7a2e310c0290f5803b38df01/kuzu-0.9.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:bda6d845bf1c7da204ffa7730573118f2d43fe6b14b1a5d0d2845ec3d3481362", size = 4123434, upload-time = "2025-04-01T19:16:14.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/4d/48eaa2ba8222b7d2702bd806c566d29a365ba91b7258e9921d2c8275ba23/kuzu-0.9.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab5b28f101c93899fc15668b6cb25f6db3d4a9844fcc4affed293caaaafaa4b7", size = 6083037, upload-time = "2025-04-01T19:16:16.31Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/f4/38f363b9e43a610d7b0d93eb38ba7359984dca01c8d1f4f6f469077d99d6/kuzu-0.9.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183bb1de19ffec1c3b07c0b4d5eecf02eb4eeafc1d50aea409bc91e1fad4d6d2", size = 6878892, upload-time = "2025-04-01T19:16:18.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/f3/6465cf09b9aaedd36154c869acd27acd077701c059aaed2ec8815981cd8e/kuzu-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:2e36ce7da1bbebb538082656de18a717895d9352a33c8bcac170ef2fc22a4902", size = 4174749, upload-time = "2025-04-01T19:16:20.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/a8/47815aefdf1c71c7a505252f717a4dee2778f994c544ca7d149fc08b092c/kuzu-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82dd690d823df816e7826945e5243a4ae65e3e948ef512709a59205b84b9f6dd", size = 3668928, upload-time = "2025-04-01T19:16:22.324Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f8/ec1b54fd635d99e764f5d9b6955a8d331cd1ad504ee922b2633847777576/kuzu-0.9.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:c394e019a14e9c5636228cf1acd333997c31e5da3d9a60a1df2c03b828438432", size = 4125076, upload-time = "2025-04-01T19:16:24.404Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/b6/40db1dec30b7236362473efc32c10f75d0874d3ed9a5bd8b7a0c614a3dd3/kuzu-0.9.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7d493f88ed31eada4b88a92b115bc6085c60498c47336ab06a489e75a727bab", size = 6084606, upload-time = "2025-04-01T19:16:27.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/84/7b3ee53a8de3b21dc73e5659ddd88a3c4d527fdec803e86d37ae1c43da67/kuzu-0.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:171b47cf2b3923c813f1ed88fb9d3964a9355129b5d3ebca54eba3450bfc1f97", size = 6878619, upload-time = "2025-04-01T19:16:28.776Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/2f/6f84eb0518352b65425cef8cae62198e6c5eceed564a5f93ca0a2faae8a9/kuzu-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:3c8a8a611f599801c8db6aeffb978cd1badcfa3ec8f79c15b701810fee71765f", size = 4175347, upload-time = "2025-04-01T19:16:31.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/57/f27a0a0e59e3925e85e03516d978289b66a1c98326c85a0d702d43e18c16/kuzu-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:509af4029f9dcb9c3e843a825df44ec30009a70fad891cbcfb611c3b8cdfefd6", size = 3669315, upload-time = "2025-04-01T19:16:33.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/96/d9d47285110eab0c9d2564b6608840c8fca5c95cfe9ce7dfcc791d3293b2/kuzu-0.9.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:885f17f6e46c15ecef121fc57a941f8b60f0a5c1d3995813bb7a4c7437fb2259", size = 4127134, upload-time = "2025-04-01T19:16:36.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/fe/ea1263767a4067a666f6402d78a7cc23724392dd09a7100f8309a1305bc6/kuzu-0.9.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f2e35aa345b543a4a21de0e82b70eac4c753987cfa4ded75ae7f9f23edbf11", size = 6083859, upload-time = "2025-04-01T19:16:38.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/ce/edd4a180a1cdb2c945832392f68f3c988d2efdb0620465d6cc846d9058a0/kuzu-0.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:67430c9813607a3b901c4a1e6bfb3b93538af230bc821e675c552a162818f589", size = 6878571, upload-time = "2025-04-01T19:16:39.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/78/d6b79c6d3c8c57a3d980a5ecf5df8a281269208a60b3d8abeabb8c6ce579/kuzu-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:549f4a72f815554fb998582876c5875cb0917a192e6a58d196e8247fd8902701", size = 4175988, upload-time = "2025-04-01T19:16:41.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/3e/73d6c92ee872182de2602c53379667b976e4140e173e8541b37ac198fbdd/kuzu-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ec2e709599b4015d0a179a191dd7850e7bf076f83b37b70d0dc2e4ee59ce7725", size = 3669449, upload-time = "2025-04-01T19:16:43.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/d7/3e2a7b8b39ea2fbe11e6c3567e08927823c42e7cc6c426464bd71cac29ac/kuzu-0.9.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:8aad4fbd74b283ffb0b115138dfc62d9775c8f19ba62ab243e55e3cd648652b6", size = 4127321, upload-time = "2025-04-01T19:16:45.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/e5/630d77dd49d9f2f5d1e35c3fd47ce75574ebfdd9081e7f56cbae35814b75/kuzu-0.9.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba9dd4f412e31d34345b6461fc9489955ae9566abf426e56af478b6e791b735a", size = 6083958, upload-time = "2025-04-01T19:16:47.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/a2/27462033d07e87eaf11c65073515a5eaad139a74af483d117dfce350c48f/kuzu-0.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340502cbce54f21a5b2440a75c28d61ddfd26d6d6848e9daa6140798bdd5b367", size = 6878543, upload-time = "2025-04-01T19:16:49.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/4a/ebe459433bd4e9ae900531359756ec5d2ad1ba35c6e4053069c0bfa8545c/kuzu-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:e1ddb189dfa2aee0123dcd1a5ccc5b831a7f297233a09fccfd76294fc2f9e6bd", size = 4176144, upload-time = "2025-04-01T19:16:51.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/34/67016765830540f64288dbaed662465daef03fbacd69026b324b33e8a7f1/kuzu-0.9.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fae68db87ba48268228c89e70ed1fde2f43843d8ed6b2debaafd314c45e8542", size = 6090336, upload-time = "2025-04-01T19:16:53.565Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/d5/0a67f8994b7ecb2d7b0d6902dd78b0516f0d549db83a23f80baebee3117b/kuzu-0.9.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0279ba37c639d96f303eb6ad4481e634495be31210991d8008c385ee50b4e0a", size = 6881186, upload-time = "2025-04-01T19:16:55.58Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6128,6 +6144,25 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/54/a3/3ceaf89a17a1e1d5e7bbdfe5514aa3055d91285b37a5c8fed662969e3d56/python_iso639-2025.2.18-py3-none-any.whl", hash = "sha256:b2d471c37483a26f19248458b20e7bd96492e15368b01053b540126bcc23152f", size = 167631, upload-time = "2025-02-18T13:48:06.602Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-jose"
|
||||
version = "3.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ecdsa" },
|
||||
{ name = "pyasn1" },
|
||||
{ name = "rsa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
cryptography = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-json-logger"
|
||||
version = "3.3.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue