diff --git a/.github/workflows/docker-backend.yml b/.github/workflows/docker-backend.yml new file mode 100644 index 0000000..77ea501 --- /dev/null +++ b/.github/workflows/docker-backend.yml @@ -0,0 +1,149 @@ + +name: Build, Push and Deploy SLRmentor Backend + +on: + pull_request: + branches: + - main + types: + - closed + +permissions: + contents: read + +concurrency: + group: slrmentor-backend-deploy + cancel-in-progress: false + +jobs: + + # ========================================== + # PHASE 1: Generate Docker Image Version + # ========================================== + + version: + name: "1. Generate Version" + + if: >- + github.event.pull_request.merged == true && + github.event.pull_request.head.ref == 'backend' + + runs-on: ubuntu-latest + + outputs: + version: ${{ steps.generate.outputs.version }} + + steps: + - name: Generate version + id: generate + shell: bash + env: + COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }} + run: | + TIMESTAMP=$(date -u +"%Y%m%d.%H%M%S") + SHORT_SHA="${COMMIT_SHA:0:7}" + VERSION="v${TIMESTAMP}-${SHORT_SHA}" + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Generated version: $VERSION" + + + # ========================================== + # PHASE 2: Build and Push Docker Image + # ========================================== + + build-and-push: + name: "2. Build & Push Docker Image" + + needs: version + runs-on: ubuntu-latest + + steps: + - name: Checkout merged commit + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + + - name: Exclude environment files + shell: bash + run: | + cat >> backend/.dockerignore <<'EOF' + + # Environment files + .env + .env.* + **/.env + **/.env.* + + # Local Python environment + .venv/ + __pycache__/ + *.pyc + EOF + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: seallabuofc + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v7 + with: + context: ./backend + file: ./backend/Dockerfile + platforms: linux/amd64 + push: true + tags: | + seallabuofc/slrmentor-backend:${{ needs.version.outputs.version }} + seallabuofc/slrmentor-backend:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build summary + shell: bash + env: + VERSION: ${{ needs.version.outputs.version }} + run: | + echo "### Docker Image Published" >> "$GITHUB_STEP_SUMMARY" + echo "- Repository: seallabuofc/slrmentor-backend" >> "$GITHUB_STEP_SUMMARY" + echo "- Version: $VERSION" >> "$GITHUB_STEP_SUMMARY" + echo "- Latest: updated" >> "$GITHUB_STEP_SUMMARY" + + + # ========================================== + # PHASE 3: Deploy Image to Render + # ========================================== + + deploy: + name: "3. Deploy to Render" + + needs: + - version + - build-and-push + + runs-on: ubuntu-latest + + steps: + - name: Trigger Render deployment + shell: bash + env: + RENDER_DEPLOY_HOOK: ${{ secrets.RENDER_DEPLOY_HOOK }} + run: | + curl --fail --silent --show-error \ + --retry 3 \ + --request POST \ + "$RENDER_DEPLOY_HOOK" + + - name: Deployment summary + shell: bash + env: + VERSION: ${{ needs.version.outputs.version }} + run: | + echo "### SLRmentor Backend Deployment" >> "$GITHUB_STEP_SUMMARY" + echo "- Image: seallabuofc/slrmentor-backend" >> "$GITHUB_STEP_SUMMARY" + echo "- Version: $VERSION" >> "$GITHUB_STEP_SUMMARY" + echo "- Render deployment: triggered" >> "$GITHUB_STEP_SUMMARY" diff --git a/Bruno_SLRmentor/.gitignore b/Bruno_SLRmentor/.gitignore new file mode 100644 index 0000000..e19311f --- /dev/null +++ b/Bruno_SLRmentor/.gitignore @@ -0,0 +1,9 @@ +# Secrets +.env* + +# Dependencies +node_modules + +# OS files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get Chat history.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Chat history.yml new file mode 100644 index 0000000..448bb9c --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Chat history.yml @@ -0,0 +1,77 @@ +info: + name: Test Get Chat history + type: http + seq: 4 + +http: + method: POST + url: https://api.slrmentor.ca/getchathistory + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"2e8b4de7\"\r + + }\r + + // 1fc49d43" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get Criteria Chat history.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Criteria Chat history.yml new file mode 100644 index 0000000..7611097 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Criteria Chat history.yml @@ -0,0 +1,75 @@ +info: + name: Test Get Criteria Chat history + type: http + seq: 8 + +http: + method: POST + url: http://127.0.0.1:5000/getcriteriachathistory + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"2e8b4de7\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get Mentor history.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Mentor history.yml new file mode 100644 index 0000000..54e573e --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Mentor history.yml @@ -0,0 +1,75 @@ +info: + name: Test Get Mentor history + type: http + seq: 7 + +http: + method: POST + url: https://api.slrmentor.ca/getmentorchathistory + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"2e8b4de7\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context.yml new file mode 100644 index 0000000..4aa0784 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context.yml @@ -0,0 +1,75 @@ +info: + name: Test Get System Context + type: http + seq: 5 + +http: + method: POST + url: https://api.slrmentor.ca/getsystemcontext + body: + type: json + data: " {\r + + \ \"hash_plain_text\": \"624a1591\"\r + + \ }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context_1.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context_1.yml new file mode 100644 index 0000000..e8716b9 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context_1.yml @@ -0,0 +1,75 @@ +info: + name: Test Get System Context_1 + type: http + seq: 14 + +http: + method: POST + url: http://127.0.0.1:5000/getsystemcontext + body: + type: json + data: " {\r + + \ \"hash_plain_text\": \"c16ada85\"\r + + \ }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Mentor.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Mentor.yml new file mode 100644 index 0000000..9a74203 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Mentor.yml @@ -0,0 +1,79 @@ +info: + name: Test Mentor + type: http + seq: 12 + +http: + method: POST + url: http://127.0.0.1:5000/mentor + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"2e8b4de7\",\r + + \ \"user_message\": \"What is a systematic map?\"\r + + \r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Response.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Response.yml new file mode 100644 index 0000000..fe85a96 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Response.yml @@ -0,0 +1,75 @@ +info: + name: Test Response + type: http + seq: 1 + +http: + method: GET + url: https://api.slrmentor.ca/ + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Status code is 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Validate that the response is json object\r + + test(\"Response is a json object\", function () {\r + + \ expect(res.getBody()).to.be.an('object');\r + + });\r + + \r + + // Validate that the response has an arg object\r + + test(\"Response to have 'arg' object\", function () {\r + + \ expect(res.getBody()).to.have.property('args');\r + + });\r + + \r + + // Validate that the response has headers object\r + + test(\"Response to have 'headers' object\", function () {\r + + \ expect(res.getBody()).to.have.property('headers');\r + + });\r + + \r + + // Validate that the response has url property\r + + test(\"Response to have 'url' property\", function () {\r + + \ expect(res.getBody()).to.have.property('url');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: |- + The tests in this request validate that: + + - the response code is 200 + - the response is a JSON object + - the response has an 'arg' object + - the response has 'headers' object + - the response has a 'URL' property diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test conversion formats.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test conversion formats.yml new file mode 100644 index 0000000..53fc6e7 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test conversion formats.yml @@ -0,0 +1,75 @@ +info: + name: Test conversion formats + type: http + seq: 2 + +http: + method: GET + url: https://api.slrmentor.ca/conversionformats + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Status code is 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Validate that the response is json object\r + + test(\"Response is a json object\", function () {\r + + \ expect(res.getBody()).to.be.an('object');\r + + });\r + + \r + + // Validate that the response has an arg object\r + + test(\"Response to have 'arg' object\", function () {\r + + \ expect(res.getBody()).to.have.property('args');\r + + });\r + + \r + + // Validate that the response has headers object\r + + test(\"Response to have 'headers' object\", function () {\r + + \ expect(res.getBody()).to.have.property('headers');\r + + });\r + + \r + + // Validate that the response has url property\r + + test(\"Response to have 'url' property\", function () {\r + + \ expect(res.getBody()).to.have.property('url');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: |- + The tests in this request validate that: + + - the response code is 200 + - the response is a JSON object + - the response has an 'arg' object + - the response has 'headers' object + - the response has a 'URL' property diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test convert string.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test convert string.yml new file mode 100644 index 0000000..8c2e017 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test convert string.yml @@ -0,0 +1,77 @@ +info: + name: Test convert string + type: http + seq: 13 + +http: + method: POST + url: https://api.slrmentor.ca/convertsearchstring + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"8e75ffec\",\r + + \ \"conversion_format\": \"Google Scholar\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test criteria.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test criteria.yml new file mode 100644 index 0000000..7d84444 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test criteria.yml @@ -0,0 +1,77 @@ +info: + name: Test criteria + type: http + seq: 10 + +http: + method: POST + url: http://127.0.0.1:5000/criteria + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"2e8b4de7\",\r + + \ \"user_message\": \"the goal of my research is to see the effects of internet usage on young children?\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test generate hash.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test generate hash.yml new file mode 100644 index 0000000..bb14be8 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test generate hash.yml @@ -0,0 +1,75 @@ +info: + name: Test generate hash + type: http + seq: 3 + +http: + method: POST + url: https://api.slrmentor.ca/createchat + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Status code is 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Validate that the response is json object\r + + test(\"Response is a json object\", function () {\r + + \ expect(res.getBody()).to.be.an('object');\r + + });\r + + \r + + // Validate that the response has an arg object\r + + test(\"Response to have 'arg' object\", function () {\r + + \ expect(res.getBody()).to.have.property('args');\r + + });\r + + \r + + // Validate that the response has headers object\r + + test(\"Response to have 'headers' object\", function () {\r + + \ expect(res.getBody()).to.have.property('headers');\r + + });\r + + \r + + // Validate that the response has url property\r + + test(\"Response to have 'url' property\", function () {\r + + \ expect(res.getBody()).to.have.property('url');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: |- + The tests in this request validate that: + + - the response code is 200 + - the response is a JSON object + - the response has an 'arg' object + - the response has 'headers' object + - the response has a 'URL' property diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test prompt.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test prompt.yml new file mode 100644 index 0000000..8d38620 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test prompt.yml @@ -0,0 +1,77 @@ +info: + name: Test prompt + type: http + seq: 9 + +http: + method: POST + url: http://127.0.0.1:5000/prompt + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"2e8b4de7\",\r + + \ \"user_message\": \"what are the effects of phone on young children? This is my reserach question\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test ragquery.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test ragquery.yml new file mode 100644 index 0000000..4b26c3d --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test ragquery.yml @@ -0,0 +1,77 @@ +info: + name: Test ragquery + type: http + seq: 11 + +http: + method: POST + url: http://127.0.0.1:5000/criteria + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"2e8b4de7\",\r + + \ \"user_message\": \"the goal of my research is to see the effects of internet usage on young children?\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context.yml new file mode 100644 index 0000000..011621b --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context.yml @@ -0,0 +1,77 @@ +info: + name: Test set System Context + type: http + seq: 6 + +http: + method: POST + url: https://api.slrmentor.ca/setsystemcontext + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\",\r + + \ \"system_context\": \"skibi\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context_1.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context_1.yml new file mode 100644 index 0000000..7a7f5ef --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context_1.yml @@ -0,0 +1,77 @@ +info: + name: Test set System Context_1 + type: http + seq: 15 + +http: + method: POST + url: http://127.0.0.1:5000/setsystemcontext + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\",\r + + \ \"system_context\": \"test\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Cloud Testing/opencollection.yml b/Bruno_SLRmentor/collections/Cloud Testing/opencollection.yml new file mode 100644 index 0000000..4397fc1 --- /dev/null +++ b/Bruno_SLRmentor/collections/Cloud Testing/opencollection.yml @@ -0,0 +1,43 @@ +opencollection: 1.0.0 + +info: + name: Cloud Testing + +request: + variables: + - name: baseUrl + value: https://postman-echo.com + +docs: + content: |- + # About this collection + + This collection helps you set up contract tests to ensure that two separate systems are compatible and can communicate with one another. + + ## **Using the collection** + + **Step 1:** Send the sample requests and view the response and test results. + + **Step 2:** Replace the sample request URLs with your desired API endpoints. + + **Step 3:** Customize the tests in the "Tests" tab if needed. Don't forget to save your changes. + + ### Resources + + [Scripting in Postman](https://learning.postman.com/docs/writing-scripts/intro-to-scripts/) + + [Test script examples](https://learning.postman.com/docs/writing-scripts/script-references/test-examples/) + + [Postman Sandbox API reference](https://learning.postman.com/docs/sending-requests/grpc/postman-sandbox-api/#writing-assertions) + + [Using the Collection Runner](https://learning.postman.com/docs/collections/running-collections/intro-to-collection-runs/) + + [ + ](https://postman.postman.co/documentation/24552533-5bead607-0197-4e1d-87d4-3160bc7631c1?entity=&branch=&version=) + type: text/markdown +bundled: false +extensions: + bruno: + ignore: + - node_modules + - .git diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Get Chat history.yml b/Bruno_SLRmentor/collections/Route Testing/Test Get Chat history.yml new file mode 100644 index 0000000..13dc2c3 --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test Get Chat history.yml @@ -0,0 +1,77 @@ +info: + name: Test Get Chat history + type: http + seq: 4 + +http: + method: POST + url: http://127.0.0.1:5000/getchathistory + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\"\r + + }\r + + // 1fc49d43" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Get Criteria Chat history.yml b/Bruno_SLRmentor/collections/Route Testing/Test Get Criteria Chat history.yml new file mode 100644 index 0000000..2fc7bbf --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test Get Criteria Chat history.yml @@ -0,0 +1,75 @@ +info: + name: Test Get Criteria Chat history + type: http + seq: 8 + +http: + method: POST + url: http://127.0.0.1:5000/getcriteriachathistory + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Get Mentor history.yml b/Bruno_SLRmentor/collections/Route Testing/Test Get Mentor history.yml new file mode 100644 index 0000000..15a459a --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test Get Mentor history.yml @@ -0,0 +1,75 @@ +info: + name: Test Get Mentor history + type: http + seq: 7 + +http: + method: POST + url: http://127.0.0.1:5000/getmentorchathistory + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Get System Context.yml b/Bruno_SLRmentor/collections/Route Testing/Test Get System Context.yml new file mode 100644 index 0000000..a792243 --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test Get System Context.yml @@ -0,0 +1,75 @@ +info: + name: Test Get System Context + type: http + seq: 5 + +http: + method: POST + url: http://127.0.0.1:5000/getsystemcontext + body: + type: json + data: " {\r + + \ \"hash_plain_text\": \"c16ada85\"\r + + \ }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Mentor.yml b/Bruno_SLRmentor/collections/Route Testing/Test Mentor.yml new file mode 100644 index 0000000..91b09ed --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test Mentor.yml @@ -0,0 +1,79 @@ +info: + name: Test Mentor + type: http + seq: 12 + +http: + method: POST + url: http://127.0.0.1:5000/mentor + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\",\r + + \ \"user_message\": \"whats the structure of a systematic literacture review\"\r + + \r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Response.yml b/Bruno_SLRmentor/collections/Route Testing/Test Response.yml new file mode 100644 index 0000000..1680b4f --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test Response.yml @@ -0,0 +1,75 @@ +info: + name: Test Response + type: http + seq: 1 + +http: + method: GET + url: http://127.0.0.1:5000 + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Status code is 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Validate that the response is json object\r + + test(\"Response is a json object\", function () {\r + + \ expect(res.getBody()).to.be.an('object');\r + + });\r + + \r + + // Validate that the response has an arg object\r + + test(\"Response to have 'arg' object\", function () {\r + + \ expect(res.getBody()).to.have.property('args');\r + + });\r + + \r + + // Validate that the response has headers object\r + + test(\"Response to have 'headers' object\", function () {\r + + \ expect(res.getBody()).to.have.property('headers');\r + + });\r + + \r + + // Validate that the response has url property\r + + test(\"Response to have 'url' property\", function () {\r + + \ expect(res.getBody()).to.have.property('url');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: |- + The tests in this request validate that: + + - the response code is 200 + - the response is a JSON object + - the response has an 'arg' object + - the response has 'headers' object + - the response has a 'URL' property diff --git a/Bruno_SLRmentor/collections/Route Testing/Test conversion formats.yml b/Bruno_SLRmentor/collections/Route Testing/Test conversion formats.yml new file mode 100644 index 0000000..4a76d8f --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test conversion formats.yml @@ -0,0 +1,75 @@ +info: + name: Test conversion formats + type: http + seq: 2 + +http: + method: GET + url: http://127.0.0.1:5000/conversionformats + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Status code is 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Validate that the response is json object\r + + test(\"Response is a json object\", function () {\r + + \ expect(res.getBody()).to.be.an('object');\r + + });\r + + \r + + // Validate that the response has an arg object\r + + test(\"Response to have 'arg' object\", function () {\r + + \ expect(res.getBody()).to.have.property('args');\r + + });\r + + \r + + // Validate that the response has headers object\r + + test(\"Response to have 'headers' object\", function () {\r + + \ expect(res.getBody()).to.have.property('headers');\r + + });\r + + \r + + // Validate that the response has url property\r + + test(\"Response to have 'url' property\", function () {\r + + \ expect(res.getBody()).to.have.property('url');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: |- + The tests in this request validate that: + + - the response code is 200 + - the response is a JSON object + - the response has an 'arg' object + - the response has 'headers' object + - the response has a 'URL' property diff --git a/Bruno_SLRmentor/collections/Route Testing/Test convert string.yml b/Bruno_SLRmentor/collections/Route Testing/Test convert string.yml new file mode 100644 index 0000000..bbe60d9 --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test convert string.yml @@ -0,0 +1,77 @@ +info: + name: Test convert string + type: http + seq: 13 + +http: + method: POST + url: http://127.0.0.1:5000/convertsearchstring + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"8e75ffec\",\r + + \ \"conversion_format\": \"IEEE Xplore\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test criteria.yml b/Bruno_SLRmentor/collections/Route Testing/Test criteria.yml new file mode 100644 index 0000000..f597ef0 --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test criteria.yml @@ -0,0 +1,77 @@ +info: + name: Test criteria + type: http + seq: 10 + +http: + method: POST + url: http://127.0.0.1:5000/criteria + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\",\r + + \ \"user_message\": \"My research is about the effect of phones on teenagers\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test generate hash.yml b/Bruno_SLRmentor/collections/Route Testing/Test generate hash.yml new file mode 100644 index 0000000..bb14be8 --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test generate hash.yml @@ -0,0 +1,75 @@ +info: + name: Test generate hash + type: http + seq: 3 + +http: + method: POST + url: https://api.slrmentor.ca/createchat + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Status code is 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Validate that the response is json object\r + + test(\"Response is a json object\", function () {\r + + \ expect(res.getBody()).to.be.an('object');\r + + });\r + + \r + + // Validate that the response has an arg object\r + + test(\"Response to have 'arg' object\", function () {\r + + \ expect(res.getBody()).to.have.property('args');\r + + });\r + + \r + + // Validate that the response has headers object\r + + test(\"Response to have 'headers' object\", function () {\r + + \ expect(res.getBody()).to.have.property('headers');\r + + });\r + + \r + + // Validate that the response has url property\r + + test(\"Response to have 'url' property\", function () {\r + + \ expect(res.getBody()).to.have.property('url');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: |- + The tests in this request validate that: + + - the response code is 200 + - the response is a JSON object + - the response has an 'arg' object + - the response has 'headers' object + - the response has a 'URL' property diff --git a/Bruno_SLRmentor/collections/Route Testing/Test prompt.yml b/Bruno_SLRmentor/collections/Route Testing/Test prompt.yml new file mode 100644 index 0000000..3470c71 --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test prompt.yml @@ -0,0 +1,77 @@ +info: + name: Test prompt + type: http + seq: 9 + +http: + method: POST + url: http://127.0.0.1:5000/prompt + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\",\r + + \ \"user_message\": \"what is a search string?\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test ragquery.yml b/Bruno_SLRmentor/collections/Route Testing/Test ragquery.yml new file mode 100644 index 0000000..36b372c --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test ragquery.yml @@ -0,0 +1,74 @@ +info: + name: Test ragquery + type: http + seq: 11 + +http: + method: POST + url: http://127.0.0.1:5000/ragquery + body: + type: json + data: |- + { + "query": "What is a systematic literacture review?" + } + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/Test set System Context.yml b/Bruno_SLRmentor/collections/Route Testing/Test set System Context.yml new file mode 100644 index 0000000..a3f8d42 --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/Test set System Context.yml @@ -0,0 +1,77 @@ +info: + name: Test set System Context + type: http + seq: 6 + +http: + method: POST + url: http://127.0.0.1:5000/setsystemcontext + body: + type: json + data: "{\r + + \ \"hash_plain_text\": \"624a1591\",\r + + \ \"system_context\": \"test\"\r + + }" + auth: inherit + +runtime: + scripts: + - type: after-response + code: "// Validate that the response code should be 200\r + + test(\"Response Code should be 200\", function () {\r + + \ expect(res.getStatus()).to.equal(200);\r + + });\r + + \r + + // Run validations on response headers like Content-Type\r + + test(\"Content-Type should be JSON\", function () {\r + + \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r + + });\r + + \r + + const json = res.getBody();\r + + \r + + // The response body, including individual attributes can be evaluated for correctness\r + + test(\"`args` should contain the correct query params\", function () {\r + + \ expect(json.args).to.be.an('object');\r + + \ expect(json.args.foo).to.eql('bar');\r + + \ expect(json.args.baz).to.eql('value');\r + + });\r + + \r + + // Non-trivial data types like Dates can also be tested against\r + + test(\"Date in the header should be valid\", function () {\r + + \ const parsedDate = new Date(res.getHeader('Date'));\r + + \ expect(parsedDate).to.be.a('date');\r + + });" + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent. diff --git a/Bruno_SLRmentor/collections/Route Testing/opencollection.yml b/Bruno_SLRmentor/collections/Route Testing/opencollection.yml new file mode 100644 index 0000000..048cc1e --- /dev/null +++ b/Bruno_SLRmentor/collections/Route Testing/opencollection.yml @@ -0,0 +1,43 @@ +opencollection: 1.0.0 + +info: + name: Route Testing + +request: + variables: + - name: baseUrl + value: https://postman-echo.com + +docs: + content: |- + # About this collection + + This collection helps you set up contract tests to ensure that two separate systems are compatible and can communicate with one another. + + ## **Using the collection** + + **Step 1:** Send the sample requests and view the response and test results. + + **Step 2:** Replace the sample request URLs with your desired API endpoints. + + **Step 3:** Customize the tests in the "Tests" tab if needed. Don't forget to save your changes. + + ### Resources + + [Scripting in Postman](https://learning.postman.com/docs/writing-scripts/intro-to-scripts/) + + [Test script examples](https://learning.postman.com/docs/writing-scripts/script-references/test-examples/) + + [Postman Sandbox API reference](https://learning.postman.com/docs/sending-requests/grpc/postman-sandbox-api/#writing-assertions) + + [Using the Collection Runner](https://learning.postman.com/docs/collections/running-collections/intro-to-collection-runs/) + + [ + ](https://postman.postman.co/documentation/24552533-5bead607-0197-4e1d-87d4-3160bc7631c1?entity=&branch=&version=) + type: text/markdown +bundled: false +extensions: + bruno: + ignore: + - node_modules + - .git diff --git a/Bruno_SLRmentor/workspace.yml b/Bruno_SLRmentor/workspace.yml new file mode 100644 index 0000000..d010ad7 --- /dev/null +++ b/Bruno_SLRmentor/workspace.yml @@ -0,0 +1,14 @@ +opencollection: 1.0.0 +info: + name: "SLRmentor" + type: workspace + +collections: + - name: "Cloud Testing" + path: "collections/Cloud Testing" + - name: "Route Testing" + path: "collections/Route Testing" + +specs: + +docs: '' diff --git a/RAG/.gitignore b/RAG/.gitignore new file mode 100644 index 0000000..11edc3b --- /dev/null +++ b/RAG/.gitignore @@ -0,0 +1,6 @@ +.venv +venv +__pycache__ +.env +.venv/ +__pycache__/ \ No newline at end of file diff --git a/RAG/1_testconnection.py b/RAG/1_testconnection.py deleted file mode 100644 index 07ab2b1..0000000 --- a/RAG/1_testconnection.py +++ /dev/null @@ -1,20 +0,0 @@ -from pymongo.mongo_client import MongoClient -from pymongo.server_api import ServerApi -from dotenv import load_dotenv -import os - -# Load environment variables from .env -load_dotenv() - -# Get the URI from the environment -uri = os.getenv("MONGODB_URI") - -# Create a new client and connect to the server -client = MongoClient(uri, server_api=ServerApi('1')) - -# Send a ping to confirm a successful connection -try: - client.admin.command('ping') - print("✅ Pinged your deployment. You successfully connected to MongoDB!") -except Exception as e: - print("❌ Connection failed:", e) diff --git a/RAG/RagPipelineTest.py b/RAG/RagPipelineTest.py deleted file mode 100644 index 62e86a5..0000000 --- a/RAG/RagPipelineTest.py +++ /dev/null @@ -1,132 +0,0 @@ -import os -import pprint -from dotenv import load_dotenv -from pathlib import Path -from pymongo import MongoClient - -# LangChain modules (1.x) -from langchain_community.embeddings import HuggingFaceEmbeddings -from langchain_community.document_loaders import PyPDFLoader -from langchain_community.llms import GPT4All -from langchain_community.vectorstores import MongoDBAtlasVectorSearch -from langchain_text_splitters import RecursiveCharacterTextSplitter -from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler -from langchain_core.prompts import PromptTemplate -from langchain_core.output_parsers import StrOutputParser -from langchain_core.runnables import RunnablePassthrough - - -# ----------------------------- -# 1. Load environment & MongoDB URI -# ----------------------------- -load_dotenv() -MONGODB_URI = os.getenv("MONGO_URI") -if not MONGODB_URI: - raise ValueError("❌ MONGO_URI not found in .env file") - -DB_NAMESPACE = "SLRMentor.document_rag" # your database.collection - -# ----------------------------- -# 2. Load embedding model -# ----------------------------- -print("🔹 Loading embedding model...") -embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") - -# ----------------------------- -# 3. Connect to MongoDB Atlas vector store -# ----------------------------- -print("🔹 Connecting to MongoDB Atlas...") -vector_store = MongoDBAtlasVectorSearch.from_connection_string( - connection_string=MONGODB_URI, - namespace=DB_NAMESPACE, - embedding=embedding_model, - index_name="vector_index" -) - -# ----------------------------- -# 4. Load PDFs and split into chunks -# ----------------------------- -docs_folder = Path("documents") -all_docs = [] - -for pdf_file in docs_folder.glob("*.pdf"): - print(f"📄 Loading PDF: {pdf_file}") - loader = PyPDFLoader(str(pdf_file)) - data = loader.load() - - print(f"✂️ Splitting PDF into chunks...") - text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20) - chunks = text_splitter.split_documents(data) - all_docs.extend(chunks) - -print(f"📄 Total chunks to process: {len(all_docs)}") - -# ----------------------------- -# 5. Upload vectors only if collection empty -# ----------------------------- -db_name, coll_name = DB_NAMESPACE.split(".") -mongo_client = MongoClient(MONGODB_URI) -collection = mongo_client[db_name][coll_name] - -existing_docs = collection.count_documents({}) - -if existing_docs > 0: - print(f"⚠️ Collection already contains {existing_docs} documents — skipping upload.") -else: - print("📥 Adding documents to MongoDB vector store...") - vector_store.add_documents(all_docs) - print("✅ Documents added successfully.") - vector_store.create_vector_search_index(dimensions=384) # MiniLM dimensions - print("🧱 Vector search index created.") - -# ----------------------------- -# 6. Load local LLM -# ----------------------------- -local_model_path = "./mistral-7b-openorca.gguf2.Q4_0.gguf" -print(f"🤖 Loading local model from {local_model_path}...") -callbacks = [StreamingStdOutCallbackHandler()] -llm = GPT4All(model=local_model_path, callbacks=callbacks, verbose=True, device="cpu") - -# ----------------------------- -# 7. Create RAG chain -# ----------------------------- -retriever = vector_store.as_retriever() - -template = """ -Use the following pieces of context to answer the question at the end. - -{context} - -Question: {question} -""" -custom_rag_prompt = PromptTemplate.from_template(template) - -def format_docs(docs): - return "\n\n".join(doc.page_content for doc in docs) - -rag_chain = ( - {"context": retriever | format_docs, "question": RunnablePassthrough()} - | custom_rag_prompt - | llm - | StrOutputParser() -) - -# ----------------------------- -# 8. Interactive question loop -# ----------------------------- -print("\n💬 Ready! Ask a question about the documents (type 'exit' to quit)\n") - -while True: - question = input("Question: ").strip() - if question.lower() in ["exit", "quit"]: - print("👋 Exiting.") - break - - print("\n🧠 Thinking...\n") - answer = rag_chain.invoke(question) - print("\nAnswer:", answer) - - documents = retriever.invoke(question) - print("\n📚 Source documents:") - pprint.pprint(documents[:2]) # show first 2 - print("-" * 80) diff --git a/RAG/env_linux.yml b/RAG/env_linux.yml deleted file mode 100644 index 399d947..0000000 --- a/RAG/env_linux.yml +++ /dev/null @@ -1,204 +0,0 @@ -name: seallapp -channels: - - defaults -dependencies: - - _libgcc_mutex=0.1=main - - _openmp_mutex=5.1=1_gnu - - bzip2=1.0.8=h5eee18b_6 - - ca-certificates=2025.7.15=h06a4308_0 - - expat=2.7.1=h6a678d5_0 - - ld_impl_linux-64=2.40=h12ee557_0 - - libffi=3.4.4=h6a678d5_1 - - libgcc-ng=11.2.0=h1234567_1 - - libgomp=11.2.0=h1234567_1 - - libstdcxx-ng=11.2.0=h1234567_1 - - libuuid=1.41.5=h5eee18b_0 - - libxcb=1.17.0=h9b100fa_0 - - ncurses=6.5=h7934f7d_0 - - openssl=3.0.17=h5eee18b_0 - - pip=25.1=pyhc872135_2 - - pthread-stubs=0.3=h0ce48e5_1 - - python=3.10.18=h1a3bd86_0 - - readline=8.3=hc2a1206_0 - - setuptools=78.1.1=py310h06a4308_0 - - sqlite=3.50.2=hb25bd0a_1 - - tk=8.6.15=h54e0aa7_0 - - wheel=0.45.1=py310h06a4308_0 - - xorg-libx11=1.8.12=h9b100fa_1 - - xorg-libxau=1.0.12=h9b100fa_0 - - xorg-libxdmcp=1.1.5=h9b100fa_0 - - xorg-xorgproto=2024.1=h5eee18b_1 - - xz=5.6.4=h5eee18b_1 - - zlib=1.2.13=h5eee18b_1 - - pip: - - aiohappyeyeballs==2.6.1 - - aiohttp==3.12.13 - - aiosignal==1.4.0 - - alabaster==1.0.0 - - annotated-types==0.7.0 - - anyio==4.9.0 - - arrow==1.3.0 - - async-timeout==4.0.3 - - attrs==25.3.0 - - babel==2.17.0 - - bcrypt==4.3.0 - - beautifulsoup4==4.13.4 - - bibtexparser==1.4.3 - - blinker==1.9.0 - - cachetools==5.5.2 - - certifi==2025.4.26 - - cffi==1.17.1 - - charset-normalizer==3.4.2 - - click==8.2.1 - - colorama==0.4.6 - - dataclasses-json==0.6.7 - - deprecated==1.2.18 - - distro==1.9.0 - - dnspython==1.16.0 - - docutils==0.21.2 - - dotenv==0.9.9 - - et-xmlfile==2.0.0 - - exceptiongroup==1.3.0 - - fake-useragent==2.2.0 - - filelock==3.20.0 - - flask==3.1.1 - - flask-cors==6.0.0 - - flask-pymongo==3.0.1 - - free-proxy==1.1.3 - - frozenlist==1.7.0 - - fsspec==2025.9.0 - - google==3.0.0 - - google-ai-generativelanguage==0.6.15 - - google-api-core==2.25.1rc0 - - google-api-python-client==2.172.0 - - google-auth==2.40.3 - - google-auth-httplib2==0.2.0 - - google-genai==1.19.0 - - google-generativeai==0.8.5 - - googleapis-common-protos==1.70.0 - - gpt4all==2.8.2 - - greenlet==3.2.4 - - grpcio==1.73.0 - - grpcio-status==1.71.0 - - gunicorn==23.0.0 - - h11==0.16.0 - - hf-xet==1.1.10 - - httpcore==1.0.9 - - httplib2==0.22.0 - - httpx==0.28.1 - - httpx-sse==0.4.3 - - huggingface-hub==0.35.3 - - idna==3.10 - - imagesize==1.4.1 - - itsdangerous==2.2.0 - - jinja2==3.1.6 - - jiter==0.10.0 - - joblib==1.5.2 - - jsonpatch==1.33 - - jsonpointer==3.0.0 - - langchain==0.3.27 - - langchain-community==0.3.31 - - langchain-core==0.3.79 - - langchain-huggingface==0.3.1 - - langchain-mongodb==0.7.0 - - langchain-text-splitters==0.3.11 - - langsmith==0.4.34 - - lark==1.3.0 - - lxml==6.0.0 - - markupsafe==3.0.2 - - marshmallow==3.26.1 - - mpmath==1.3.0 - - multidict==6.6.3 - - mypy-extensions==1.1.0 - - networkx==3.4.2 - - numpy==2.2.6 - - nvidia-cublas-cu12==12.8.4.1 - - nvidia-cuda-cupti-cu12==12.8.90 - - nvidia-cuda-nvrtc-cu12==12.8.93 - - nvidia-cuda-runtime-cu12==12.8.90 - - nvidia-cudnn-cu12==9.10.2.21 - - nvidia-cufft-cu12==11.3.3.83 - - nvidia-cufile-cu12==1.13.1.3 - - nvidia-curand-cu12==10.3.9.90 - - nvidia-cusolver-cu12==11.7.3.90 - - nvidia-cusparse-cu12==12.5.8.93 - - nvidia-cusparselt-cu12==0.7.1 - - nvidia-nccl-cu12==2.27.3 - - nvidia-nvjitlink-cu12==12.8.93 - - nvidia-nvtx-cu12==12.8.90 - - openai==1.93.0 - - openpyxl==3.1.5 - - orjson==3.11.3 - - outcome==1.3.0.post0 - - packaging==25.0 - - pandas==2.3.0 - - pillow==11.3.0 - - propcache==0.3.2 - - proto-plus==1.26.1 - - protobuf==5.29.5 - - pyasn1==0.6.1 - - pyasn1-modules==0.4.2 - - pycparser==2.22 - - pycurl==7.45.6 - - pydantic==2.11.5 - - pydantic-core==2.33.2 - - pydantic-settings==2.11.0 - - pygments==2.19.2 - - pymongo==4.15.3 - - pyparsing==3.2.3 - - pypdf==6.1.1 - - pysocks==1.7.1 - - python-dateutil==2.9.0.post0 - - python-dotenv==1.1.0 - - pytz==2025.2 - - pyyaml==6.0.3 - - regex==2025.9.18 - - requests==2.32.5 - - requests-toolbelt==1.0.0 - - rsa==4.9.1 - - safetensors==0.6.2 - - scholarly==1.7.11 - - scikit-learn==1.7.2 - - scipy==1.15.3 - - selenium==4.34.0 - - sentence-transformers==5.1.1 - - six==1.17.0 - - sniffio==1.3.1 - - snowballstemmer==3.0.1 - - sortedcontainers==2.4.0 - - soupsieve==2.7 - - sphinx==8.1.3 - - sphinx-rtd-theme==3.0.2 - - sphinxcontrib-applehelp==2.0.0 - - sphinxcontrib-devhelp==2.0.0 - - sphinxcontrib-htmlhelp==2.1.0 - - sphinxcontrib-jquery==4.1 - - sphinxcontrib-jsmath==1.0.1 - - sphinxcontrib-qthelp==2.0.0 - - sphinxcontrib-serializinghtml==2.0.0 - - sqlalchemy==2.0.44 - - sympy==1.14.0 - - tenacity==9.1.2 - - threadpoolctl==3.6.0 - - tokenizers==0.22.1 - - tomli==2.2.1 - - torch==2.8.0 - - tqdm==4.67.1 - - transformers==4.57.0 - - trio==0.30.0 - - trio-websocket==0.12.2 - - triton==3.4.0 - - types-python-dateutil==2.9.0.20250516 - - typing-extensions==4.14.0 - - typing-inspect==0.9.0 - - typing-inspection==0.4.1 - - tzdata==2025.2 - - uritemplate==4.2.0 - - urllib3==2.4.0 - - websocket-client==1.8.0 - - websockets==15.0.1 - - werkzeug==3.1.3 - - wrapt==1.17.2 - - wsproto==1.2.0 - - yarl==1.20.1 - - zstandard==0.25.0 diff --git a/RAG/env_windows.yml b/RAG/env_windows.yml deleted file mode 100644 index 0940288..0000000 --- a/RAG/env_windows.yml +++ /dev/null @@ -1,190 +0,0 @@ -name: seallapp -channels: - - defaults - - https://repo.anaconda.com/pkgs/main - - https://repo.anaconda.com/pkgs/r - - https://repo.anaconda.com/pkgs/msys2 -dependencies: - - bzip2=1.0.8=h2bbff1b_6 - - ca-certificates=2025.9.9=haa95532_0 - - expat=2.7.1=h8ddb27b_0 - - libffi=3.4.4=hd77b12b_1 - - libzlib=1.3.1=h02ab6af_0 - - openssl=3.0.18=h543e019_0 - - pip=25.2=pyhc872135_1 - - python=3.10.19=h981015d_0 - - setuptools=80.9.0=py310haa95532_0 - - sqlite=3.50.2=hda9a48d_1 - - tk=8.6.15=hf199647_0 - - ucrt=10.0.22621.0=haa95532_0 - - vc=14.3=h2df5915_10 - - vc14_runtime=14.44.35208=h4927774_10 - - vs2015_runtime=14.44.35208=ha6b5a95_10 - - wheel=0.45.1=py310haa95532_0 - - xz=5.6.4=h4754444_1 - - zlib=1.3.1=h02ab6af_0 - - pip: - - aiohappyeyeballs==2.6.1 - - aiohttp==3.12.13 - - aiosignal==1.4.0 - - alabaster==1.0.0 - - annotated-types==0.7.0 - - anyio==4.9.0 - - arrow==1.3.0 - - async-timeout==4.0.3 - - attrs==25.3.0 - - babel==2.17.0 - - bcrypt==4.3.0 - - beautifulsoup4==4.13.4 - - bibtexparser==1.4.3 - - blinker==1.9.0 - - cachetools==5.5.2 - - certifi==2025.4.26 - - cffi==1.17.1 - - charset-normalizer==3.4.2 - - click==8.2.1 - - colorama==0.4.6 - - dataclasses-json==0.6.7 - - deprecated==1.2.18 - - distro==1.9.0 - - dnspython==2.7.0 - - docutils==0.21.2 - - dotenv==0.9.9 - - et-xmlfile==2.0.0 - - exceptiongroup==1.3.0 - - fake-useragent==2.2.0 - - filelock==3.20.0 - - flask==3.1.1 - - flask-cors==6.0.0 - - flask-pymongo==3.0.1 - - free-proxy==1.1.3 - - frozenlist==1.7.0 - - fsspec==2025.9.0 - - google==3.0.0 - - google-ai-generativelanguage==0.6.15 - - google-api-core==2.25.1rc0 - - google-api-python-client==2.172.0 - - google-auth==2.40.3 - - google-auth-httplib2==0.2.0 - - google-genai==1.19.0 - - google-generativeai==0.8.5 - - googleapis-common-protos==1.70.0 - - gpt4all==2.8.2 - - greenlet==3.2.4 - - grpcio==1.73.0 - - grpcio-status==1.71.0 - - gunicorn==23.0.0 - - h11==0.16.0 - - httpcore==1.0.9 - - httplib2==0.22.0 - - httpx==0.28.1 - - httpx-sse==0.4.3 - - huggingface-hub==0.36.0 - - idna==3.10 - - imagesize==1.4.1 - - itsdangerous==2.2.0 - - jinja2==3.1.6 - - jiter==0.10.0 - - joblib==1.5.2 - - jsonpatch==1.33 - - jsonpointer==3.0.0 - - langchain==1.0.2 - - langchain-classic==1.0.0 - - langchain-community==0.4 - - langchain-core==1.0.1 - - langchain-huggingface==1.0.0 - - langchain-mongodb==0.7.1 - - langchain-text-splitters==1.0.0 - - langchainhub==0.1.21 - - langgraph==1.0.1 - - langgraph-checkpoint==3.0.0 - - langgraph-prebuilt==1.0.1 - - langgraph-sdk==0.2.9 - - langsmith==0.4.38 - - lark==1.3.0 - - lxml==6.0.0 - - markupsafe==3.0.2 - - marshmallow==3.26.1 - - mpmath==1.3.0 - - multidict==6.6.3 - - mypy-extensions==1.1.0 - - networkx==3.4.2 - - numpy==2.2.6 - - openai==1.93.0 - - openpyxl==3.1.5 - - orjson==3.11.4 - - ormsgpack==1.11.0 - - outcome==1.3.0.post0 - - packaging==24.2 - - pandas==2.3.0 - - pillow==12.0.0 - - propcache==0.3.2 - - proto-plus==1.26.1 - - protobuf==5.29.5 - - pyasn1==0.6.1 - - pyasn1-modules==0.4.2 - - pycparser==2.22 - - pycurl==7.45.6 - - pydantic==2.11.5 - - pydantic-core==2.33.2 - - pydantic-settings==2.11.0 - - pygments==2.19.2 - - pymongo==4.15.3 - - pyparsing==3.2.3 - - pypdf==6.1.3 - - pysocks==1.7.1 - - python-dateutil==2.9.0.post0 - - python-dotenv==1.1.0 - - pytz==2025.2 - - pyyaml==6.0.3 - - regex==2025.10.23 - - requests==2.32.5 - - requests-toolbelt==1.0.0 - - rsa==4.9.1 - - safetensors==0.6.2 - - scholarly==1.7.11 - - scikit-learn==1.7.2 - - scipy==1.15.3 - - selenium==4.34.0 - - sentence-transformers==5.1.2 - - six==1.17.0 - - sniffio==1.3.1 - - snowballstemmer==3.0.1 - - sortedcontainers==2.4.0 - - soupsieve==2.7 - - sphinx==8.1.3 - - sphinx-rtd-theme==3.0.2 - - sphinxcontrib-applehelp==2.0.0 - - sphinxcontrib-devhelp==2.0.0 - - sphinxcontrib-htmlhelp==2.1.0 - - sphinxcontrib-jquery==4.1 - - sphinxcontrib-jsmath==1.0.1 - - sphinxcontrib-qthelp==2.0.0 - - sphinxcontrib-serializinghtml==2.0.0 - - sqlalchemy==2.0.44 - - sympy==1.14.0 - - tenacity==9.1.2 - - threadpoolctl==3.6.0 - - tokenizers==0.22.1 - - tomli==2.2.1 - - torch==2.9.0 - - tqdm==4.67.1 - - transformers==4.57.1 - - trio==0.30.0 - - trio-websocket==0.12.2 - - types-python-dateutil==2.9.0.20250516 - - types-requests==2.32.4.20250913 - - typing-extensions==4.14.0 - - typing-inspect==0.9.0 - - typing-inspection==0.4.1 - - tzdata==2025.2 - - uritemplate==4.2.0 - - urllib3==2.4.0 - - websocket-client==1.8.0 - - websockets==15.0.1 - - werkzeug==3.1.3 - - wrapt==1.17.2 - - wsproto==1.2.0 - - xxhash==3.6.0 - - yarl==1.20.1 - - zstandard==0.25.0 diff --git a/RAG/langchain-local-rag.ipynb b/RAG/langchain-local-rag.ipynb deleted file mode 100644 index 01760df..0000000 --- a/RAG/langchain-local-rag.ipynb +++ /dev/null @@ -1,123 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "8585862a", - "metadata": { - "vscode": { - "languageId": "plaintext" - } - }, - "outputs": [], - "source": [ - "from pymongo.mongo_client import MongoClient\n", - "from pymongo.server_api import ServerApi\n", - "from dotenv import load_dotenv\n", - "import os\n", - "\n", - "# Load environment variables from .env\n", - "load_dotenv()\n", - "\n", - "# Get the URI from the environment\n", - "uri = os.getenv(\"MONGODB_URI\")\n", - "\n", - "# Create a new client and connect to the server\n", - "client = MongoClient(uri, server_api=ServerApi('1'))\n", - "\n", - "# Send a ping to confirm a successful connection\n", - "try:\n", - " client.admin.command('ping')\n", - " print(\"✅ Pinged your deployment. You successfully connected to MongoDB!\")\n", - "except Exception as e:\n", - " print(\"❌ Connection failed:\", e)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f4e3c10", - "metadata": { - "vscode": { - "languageId": "plaintext" - } - }, - "outputs": [], - "source": [ - "from langchain_mongodb import MongoDBAtlasVectorSearch\n", - "from langchain_huggingface import HuggingFaceEmbeddings\n", - "from pymongo.mongo_client import MongoClient\n", - "from pymongo.server_api import ServerApi\n", - "from langchain_community.document_loaders import PyPDFLoader\n", - "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", - "from dotenv import load_dotenv\n", - "import os\n", - "\n", - "load_dotenv()\n", - "MONGODB_URI = os.getenv(\"MONGODB_URI\")\n", - "\n", - "\n", - "# Load the embedding model (https://huggingface.co/mixedbread-ai/mxbai-embed-large-v1)\n", - "embedding_model = HuggingFaceEmbeddings(model_name=\"mixedbread-ai/mxbai-embed-large-v1\")\n", - "\n", - "# Instantiate vector store\n", - "vector_store = MongoDBAtlasVectorSearch.from_connection_string(\n", - " connection_string = MONGODB_URI,\n", - " namespace = \"langchain_db.local_rag\",\n", - " embedding=embedding_model,\n", - " index_name=\"vector_index\"\n", - ")\n", - "\n", - "# Load the PDF\n", - "loader = PyPDFLoader(\"documents/001_Petersen.pdf\")\n", - "data = loader.load()\n", - "print(data)\n", - "\n", - "# Split PDF into documents\n", - "text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)\n", - "docs = text_splitter.split_documents(data)\n", - "\n", - "# Add data to the vector store\n", - "vector_store.add_documents(docs)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4b46165b", - "metadata": { - "vscode": { - "languageId": "plaintext" - } - }, - "outputs": [], - "source": [ - "# Use helper method to create the vector search index\n", - "vector_store.create_vector_search_index(\n", - " dimensions = 1024 # The dimensions of the vector embeddings to be indexed\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.5" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/RAG/rag-ingestion/.python-version b/RAG/rag-ingestion/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/RAG/rag-ingestion/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/RAG/rag-ingestion/README.md b/RAG/rag-ingestion/README.md new file mode 100644 index 0000000..287cdc6 --- /dev/null +++ b/RAG/rag-ingestion/README.md @@ -0,0 +1,412 @@ +# RAG Database Workflow Tools + +This directory contains utility scripts used to manage the MongoDB data and Vector Search resources used by the application's RAG workflow. + +These scripts are not a standalone application or separate RAG service. They are development and maintenance tools for working with the RAG-related MongoDB collection. + +They support common database workflows such as: + +- Extracting and chunking research papers +- Populating the RAG collection +- ingesting existing papers +- Testing semantic retrieval +- Clearing the collection when rebuilding the RAG data + +The scripts use the same .env as the backend to work + +--- + +# RAG Data Workflow + +The current default workflow uses MongoDB Atlas Automated Embedding. + +```text +Research Papers + ↓ +PDF Extraction + ↓ +Text Cleaning + ↓ +Chunking + ↓ +MongoDB Collection + ↓ +MongoDB Automated Embedding + ↓ +MongoDB Vector Search + ↓ +Retrieved Document Segments + ↓ +Backend RAG Workflow +``` + +The utilities in this directory primarily manage the part of the workflow between the source PDFs and the MongoDB Vector Search index. + +They do not generate the final RAG response. + +The backend uses the retrieved document segments as context when communicating with the application's LLM. + +--- + +# Embedding Approaches + +Two versions of the ingestion and retrieval utilities are kept in this directory. + +## MongoDB Automated Embedding + +This is the current and recommended workflow. + +Scripts: + +```text +ingest.py +test_search.py +``` + +The scripts send plain text to MongoDB. + +MongoDB Atlas is responsible for: + +```text +Document embedding +Query embedding +Vector indexing +Vector similarity search +``` + +The backend and these management scripts do not need to host or run an embedding model. + +--- + +## Local Embedding Model + +The previous implementation is retained for development, comparison, and testing. + +Scripts: + +```text +ingest_withmodel.py +test_search_withmodel.py +``` + +These scripts use: + +```text +sentence-transformers/all-MiniLM-L6-v2 +``` + +to generate embeddings locally. + +The generated vectors have: + +```text +384 dimensions +``` + +This workflow requires the Python process to load the embedding model and explicitly generate both document and query vectors. + +--- + +# Directory Structure + +```text +rag-ingestion/ +│ +├── data/ +│ ├── paper-1.pdf +│ ├── paper-2.pdf +│ └── ... +│ +├── ingest.py +├── ingest_withmodel.py +├── ingest_all.py +│ +├── test_search.py +├── test_search_withmodel.py +│ +├── delete_all.py +│ +├── pyproject.toml +├── uv.lock +└── README.md +``` + +The `data/` directory is used as the local source directory for research papers that need to be added to the RAG collection. + +--- + +# Setup + +Synchronize the project dependencies with: + +```bash +uv sync +``` + +This project uses the same `.env` file as the backend. + +--- + +# Script Overview + +| Script | Purpose | +|---|---| +| `ingest.py` | Populate or update the RAG collection using MongoDB Automated Embedding | +| `ingest_withmodel.py` | Populate the collection using locally generated MiniLM embeddings | +| `ingest_all.py` | Run `ingest.py` for every PDF in `data/` | +| `test_search.py` | Test retrieval using MongoDB Automated Embedding | +| `test_search_withmodel.py` | Test retrieval using locally generated MiniLM query embeddings | +| `delete_all.py` | Remove all documents from the configured RAG collection | + +--- + +# Scripts + +## `ingest.py` + +`ingest.py` is the primary utility for adding a single research paper to the RAG collection. + +It prepares the PDF data required by the application's RAG retrieval workflow. + +The script: + +1. Reads the PDF. +2. Extracts text page by page. +3. Cleans common PDF extraction artifacts. +4. Splits the text into sentence-aware chunks. +5. Generates a deterministic `paper_id`. +6. Stores each chunk and its metadata in MongoDB. +7. Allows the MongoDB Atlas Vector Search index to automatically embed the `content` field. + +The script does not generate embeddings locally. + +A stored document has a structure similar to: + +```json +{ + "paper_id": "abc123...", + "source": "paper.pdf", + "page": 6, + "chunk_index": 18, + "content": "The study employed a mixed-methods methodology..." +} +``` + +MongoDB Atlas handles the vector representation separately through the configured Automated Embedding index. + +Each PDF receives a deterministic `paper_id` generated from the SHA-256 hash of the file. + +This allows the same PDF to be ingested again without creating duplicate chunk entries. Existing chunks for that paper are updated instead. + +### Usage + +```bash +uv run ingest.py data/paper.pdf +``` + +Example: + +```bash +uv run ingest.py data/adhd-study.pdf +``` + +--- + +## `ingest_withmodel.py` + +`ingest_withmodel.py` preserves the previous ingestion workflow. + +It performs the same general PDF extraction and chunking process, but generates embeddings locally before storing the chunks in MongoDB. + +The workflow is: + +```text +PDF + ↓ +Text extraction + ↓ +Token-aware chunking + ↓ +all-MiniLM-L6-v2 + ↓ +384-dimensional vector + ↓ +MongoDB +``` + +The local model is: + +```text +sentence-transformers/all-MiniLM-L6-v2 +``` + +The script stores additional embedding-related information: + +- Embedding vector +- Embedding model +- Embedding dimensions + +A document created by this workflow has a structure similar to: + +```json +{ + "paper_id": "abc123...", + "source": "paper.pdf", + "page": 6, + "chunk_index": 18, + "content": "The study employed a mixed-methods methodology...", + "embedding": [ + 0.012, + -0.034, + 0.081 + ], + "embedding_model": "sentence-transformers/all-MiniLM-L6-v2", + "embedding_dimensions": 384 +} +``` + +### Usage + +```bash +uv run ingest_withmodel.py data/paper.pdf +``` + +Example: + +```bash +uv run ingest_withmodel.py data/adhd-study.pdf +``` + +This script is mainly retained for testing or comparing the previous local-embedding workflow with MongoDB Automated Embedding. + +--- + +# Ingestion Workflow Comparison + +## `ingest.py` + +```text +PDF + ↓ +Extract text + ↓ +Create chunks + ↓ +Store chunks in MongoDB + ↓ +MongoDB generates embeddings +``` + +Responsibilities of the utility: + +```text +PDF parsing +Text cleaning +Chunking +Metadata +MongoDB insertion +``` + +Responsibilities of MongoDB Atlas: + +```text +Embedding generation +Vector storage/indexing +Vector Search +``` + +--- + +## `ingest_withmodel.py` + +```text +PDF + ↓ +Extract text + ↓ +Create chunks + ↓ +Load MiniLM + ↓ +Generate embeddings + ↓ +Store chunks and vectors in MongoDB +``` + +In this workflow, embedding generation is also the responsibility of the Python utility. + +--- + +# `ingest_all.py` + +`ingest_all.py` is a convenience utility for populating the RAG collection with every PDF in the `data/` directory. + +It automatically discovers `.pdf` files and executes: + +```text +ingest.py +``` + +for each one. + +Because it uses `ingest.py`, it uses the MongoDB Automated Embedding workflow. + +For example: + +```text +data/ +├── paper-1.pdf +├── paper-2.pdf +├── paper-3.pdf +└── notes.txt +``` + +The following files will be processed: + +```text +paper-1.pdf +paper-2.pdf +paper-3.pdf +``` + +Non-PDF files are ignored. + +If one paper fails, the script continues processing the remaining papers and reports the results at the end. + +### Usage + +```bash +uv run ingest_all.py +``` + +Example output: + +```text +Found 3 PDF file(s): + - paper-1.pdf + - paper-2.pdf + - paper-3.pdf + +... + +INGESTION SUMMARY + +Total: 3 +Successful: 3 +Failed: 0 +``` + +This is the main utility for populating or rebuilding the RAG collection from the PDFs currently stored in `data/`. + +--- + +# `test_search.py` + +`test_search.py` is a diagnostic utility for testing the current MongoDB Vector Search configuration. + +It allows retrieval to be tested independently from the rest of the backend RAG workflow. + +This is useful for verifying that: + +- Papers have been correctly ingested. +- Mong \ No newline at end of file diff --git a/RAG/documents/001_Petersen.pdf b/RAG/rag-ingestion/data/001_Petersen.pdf similarity index 100% rename from RAG/documents/001_Petersen.pdf rename to RAG/rag-ingestion/data/001_Petersen.pdf diff --git a/RAG/documents/1-s2.0-S0950584908001390-main.pdf b/RAG/rag-ingestion/data/1-s2.0-S0950584908001390-main.pdf similarity index 100% rename from RAG/documents/1-s2.0-S0950584908001390-main.pdf rename to RAG/rag-ingestion/data/1-s2.0-S0950584908001390-main.pdf diff --git a/RAG/documents/Evidence_based_software_engineering.pdf b/RAG/rag-ingestion/data/Evidence_based_software_engineering.pdf similarity index 100% rename from RAG/documents/Evidence_based_software_engineering.pdf rename to RAG/rag-ingestion/data/Evidence_based_software_engineering.pdf diff --git "a/RAG/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" "b/RAG/rag-ingestion/data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" similarity index 100% rename from "RAG/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" rename to "RAG/rag-ingestion/data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" diff --git a/RAG/documents/Kitchenham-2007Systematicreviews5-8.pdf b/RAG/rag-ingestion/data/Kitchenham-2007Systematicreviews5-8.pdf similarity index 100% rename from RAG/documents/Kitchenham-2007Systematicreviews5-8.pdf rename to RAG/rag-ingestion/data/Kitchenham-2007Systematicreviews5-8.pdf diff --git a/RAG/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf b/RAG/rag-ingestion/data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf similarity index 100% rename from RAG/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf rename to RAG/rag-ingestion/data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf diff --git a/RAG/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf b/RAG/rag-ingestion/data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf similarity index 100% rename from RAG/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf rename to RAG/rag-ingestion/data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf diff --git a/RAG/documents/Rapid Reviews in Software Engineering.pdf b/RAG/rag-ingestion/data/Rapid Reviews in Software Engineering.pdf similarity index 100% rename from RAG/documents/Rapid Reviews in Software Engineering.pdf rename to RAG/rag-ingestion/data/Rapid Reviews in Software Engineering.pdf diff --git a/RAG/documents/SLR Student Guide.pdf b/RAG/rag-ingestion/data/SLR Student Guide.pdf similarity index 100% rename from RAG/documents/SLR Student Guide.pdf rename to RAG/rag-ingestion/data/SLR Student Guide.pdf diff --git a/RAG/documents/Synthesizing Evidence in Software Engineering Research.pdf b/RAG/rag-ingestion/data/Synthesizing Evidence in Software Engineering Research.pdf similarity index 100% rename from RAG/documents/Synthesizing Evidence in Software Engineering Research.pdf rename to RAG/rag-ingestion/data/Synthesizing Evidence in Software Engineering Research.pdf diff --git "a/RAG/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" "b/RAG/rag-ingestion/data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" similarity index 100% rename from "RAG/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" rename to "RAG/rag-ingestion/data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" diff --git a/RAG/documents/pizard_2024.pdf b/RAG/rag-ingestion/data/pizard_2024.pdf similarity index 100% rename from RAG/documents/pizard_2024.pdf rename to RAG/rag-ingestion/data/pizard_2024.pdf diff --git a/RAG/rag-ingestion/delete_all.py b/RAG/rag-ingestion/delete_all.py new file mode 100644 index 0000000..829dcfb --- /dev/null +++ b/RAG/rag-ingestion/delete_all.py @@ -0,0 +1,74 @@ +import os +import sys + +from dotenv import load_dotenv +from pymongo import MongoClient + + +load_dotenv() + +MONGODB_URI = os.environ["MONGO_URI"] +MONGODB_DATABASE = os.environ.get( + "MONGO_DB_NAME" +) +MONGODB_COLLECTION = os.environ.get( + "MONGO_DB_RAG_COLLECTION_NAME" +) + + +def main(): + client = MongoClient( + MONGODB_URI, + serverSelectionTimeoutMS=10000, + ) + + try: + client.admin.command("ping") + + collection = client[ + MONGODB_DATABASE + ][ + MONGODB_COLLECTION + ] + + document_count = collection.count_documents({}) + + print( + f"Database: {MONGODB_DATABASE}" + ) + print( + f"Collection: {MONGODB_COLLECTION}" + ) + print( + f"Documents: {document_count}" + ) + + if document_count == 0: + print("\nCollection is already empty.") + return + + confirmation = input( + "\nDelete ALL documents from this collection? " + "Type 'yes' to continue: " + ) + + if confirmation.lower() != "yes": + print("Cancelled.") + return + + result = collection.delete_many({}) + + print() + print( + f"Deleted {result.deleted_count} documents." + ) + print( + "Collection was NOT deleted." + ) + + finally: + client.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/RAG/rag-ingestion/documents/001_Petersen.pdf b/RAG/rag-ingestion/documents/001_Petersen.pdf new file mode 100644 index 0000000..280be87 Binary files /dev/null and b/RAG/rag-ingestion/documents/001_Petersen.pdf differ diff --git a/RAG/rag-ingestion/documents/1-s2.0-S0950584908001390-main.pdf b/RAG/rag-ingestion/documents/1-s2.0-S0950584908001390-main.pdf new file mode 100644 index 0000000..f69e221 Binary files /dev/null and b/RAG/rag-ingestion/documents/1-s2.0-S0950584908001390-main.pdf differ diff --git a/RAG/rag-ingestion/documents/Evidence_based_software_engineering.pdf b/RAG/rag-ingestion/documents/Evidence_based_software_engineering.pdf new file mode 100644 index 0000000..a09398a Binary files /dev/null and b/RAG/rag-ingestion/documents/Evidence_based_software_engineering.pdf differ diff --git "a/RAG/rag-ingestion/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" "b/RAG/rag-ingestion/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" new file mode 100644 index 0000000..6fddd9d Binary files /dev/null and "b/RAG/rag-ingestion/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" differ diff --git a/RAG/rag-ingestion/documents/Kitchenham-2007Systematicreviews5-8.pdf b/RAG/rag-ingestion/documents/Kitchenham-2007Systematicreviews5-8.pdf new file mode 100644 index 0000000..ee87046 Binary files /dev/null and b/RAG/rag-ingestion/documents/Kitchenham-2007Systematicreviews5-8.pdf differ diff --git a/RAG/rag-ingestion/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf b/RAG/rag-ingestion/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf new file mode 100644 index 0000000..4972fb7 Binary files /dev/null and b/RAG/rag-ingestion/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf differ diff --git a/RAG/rag-ingestion/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf b/RAG/rag-ingestion/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf new file mode 100644 index 0000000..c7a965b Binary files /dev/null and b/RAG/rag-ingestion/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf differ diff --git a/RAG/rag-ingestion/documents/Rapid Reviews in Software Engineering.pdf b/RAG/rag-ingestion/documents/Rapid Reviews in Software Engineering.pdf new file mode 100644 index 0000000..f527880 Binary files /dev/null and b/RAG/rag-ingestion/documents/Rapid Reviews in Software Engineering.pdf differ diff --git a/RAG/rag-ingestion/documents/SLR Student Guide.pdf b/RAG/rag-ingestion/documents/SLR Student Guide.pdf new file mode 100644 index 0000000..c92c656 Binary files /dev/null and b/RAG/rag-ingestion/documents/SLR Student Guide.pdf differ diff --git a/RAG/rag-ingestion/documents/Synthesizing Evidence in Software Engineering Research.pdf b/RAG/rag-ingestion/documents/Synthesizing Evidence in Software Engineering Research.pdf new file mode 100644 index 0000000..e720f94 Binary files /dev/null and b/RAG/rag-ingestion/documents/Synthesizing Evidence in Software Engineering Research.pdf differ diff --git "a/RAG/rag-ingestion/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" "b/RAG/rag-ingestion/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" new file mode 100644 index 0000000..2c153fd Binary files /dev/null and "b/RAG/rag-ingestion/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" differ diff --git a/RAG/rag-ingestion/documents/pizard_2024.pdf b/RAG/rag-ingestion/documents/pizard_2024.pdf new file mode 100644 index 0000000..0c8d28a Binary files /dev/null and b/RAG/rag-ingestion/documents/pizard_2024.pdf differ diff --git a/RAG/rag-ingestion/ingest.py b/RAG/rag-ingestion/ingest.py new file mode 100644 index 0000000..0519be3 --- /dev/null +++ b/RAG/rag-ingestion/ingest.py @@ -0,0 +1,613 @@ +import hashlib +import os +import re +import sys +from pathlib import Path + +from dotenv import load_dotenv +from pypdf import PdfReader +from pymongo import MongoClient, ReplaceOne + + +# ============================================================ +# Configuration +# ============================================================ + +# Chunking is word-based so we do not need to load any +# tokenizer or embedding model in the backend. +MAX_CHUNK_WORDS = 200 +OVERLAP_WORDS = 20 + +load_dotenv() + +MONGODB_URI = os.environ["MONGO_URI"] + +MONGODB_DATABASE = os.environ.get( + "MONGO_DB_NAME", + "rag_db_test", +) + +MONGODB_COLLECTION = os.environ.get( + "MONGO_DB_RAG_COLLECTION_NAME", + "test_paper_chunks", +) + + +# ============================================================ +# Helpers +# ============================================================ + +def generate_paper_id(pdf_path: Path) -> str: + """ + Generate a stable SHA-256 ID from the PDF contents. + + If the exact same PDF is ingested again, + it receives the same paper_id. + """ + + sha256 = hashlib.sha256() + + with pdf_path.open("rb") as file: + while chunk := file.read(1024 * 1024): + sha256.update(chunk) + + return sha256.hexdigest() + + +def clean_text(text: str) -> str: + """ + Clean common PDF extraction artifacts without heavily + modifying the source text. + """ + + # Remove null bytes. + text = text.replace("\x00", "") + + # Join words broken across lines: + # + # transfor- + # mation + # + # becomes: + # + # transformation + text = re.sub( + r"(?<=\w)-\s*\n\s*(?=\w)", + "", + text, + ) + + # Normalize line breaks inside paragraphs. + text = re.sub( + r"(? list[dict]: + """ + Extract text from each PDF page independently so page + metadata can be retained in MongoDB. + """ + + reader = PdfReader(pdf_path) + + pages = [] + + for page_number, page in enumerate( + reader.pages, + start=1, + ): + raw_text = page.extract_text() + + if not raw_text: + continue + + text = clean_text(raw_text) + + if not text: + continue + + pages.append( + { + "page": page_number, + "text": text, + } + ) + + return pages + + +# ============================================================ +# Chunking +# ============================================================ + +def split_into_sentences(text: str) -> list[str]: + """ + Basic sentence-aware splitter. + + Keeping sentence boundaries generally produces more useful + RAG chunks than blindly splitting every N words. + """ + + sentences = re.split( + r"(?<=[.!?])\s+", + text, + ) + + return [ + sentence.strip() + for sentence in sentences + if sentence.strip() + ] + + +def word_count(text: str) -> int: + """ + Approximate chunk size without requiring a tokenizer. + + Atlas/Voyage performs its own tokenization when generating + the actual embeddings. + """ + + return len(text.split()) + + +def split_long_sentence( + sentence: str, +) -> list[str]: + """ + Split an unusually long sentence into smaller pieces. + """ + + words = sentence.split() + + pieces = [] + + start = 0 + + while start < len(words): + + end = min( + start + MAX_CHUNK_WORDS, + len(words), + ) + + piece = " ".join( + words[start:end] + ).strip() + + if piece: + pieces.append(piece) + + if end >= len(words): + break + + start = end - OVERLAP_WORDS + + return pieces + + +def get_overlap_sentences( + sentences: list[str], +) -> list[str]: + """ + Preserve approximately OVERLAP_WORDS words from the end + of the previous chunk. + + This provides some context between adjacent chunks while + keeping duplicated embedding input relatively small. + """ + + overlap = [] + total_words = 0 + + for sentence in reversed(sentences): + + sentence_words = word_count( + sentence + ) + + if ( + total_words + sentence_words + > OVERLAP_WORDS + ): + break + + overlap.insert( + 0, + sentence, + ) + + total_words += sentence_words + + return overlap + + +def create_chunks( + text: str, +) -> list[str]: + """ + Create sentence-aware chunks using an approximate word + limit. + + No embedding model or tokenizer is required locally. + """ + + sentences = split_into_sentences(text) + + chunks = [] + + current_sentences = [] + current_words = 0 + + for sentence in sentences: + + sentence_words = word_count( + sentence + ) + + # ---------------------------------------------------- + # Handle an unusually long individual sentence + # ---------------------------------------------------- + + if sentence_words > MAX_CHUNK_WORDS: + + if current_sentences: + chunks.append( + " ".join( + current_sentences + ) + ) + + current_sentences = [] + current_words = 0 + + chunks.extend( + split_long_sentence( + sentence + ) + ) + + continue + + # ---------------------------------------------------- + # Sentence fits in current chunk + # ---------------------------------------------------- + + if ( + current_words + sentence_words + <= MAX_CHUNK_WORDS + ): + current_sentences.append( + sentence + ) + + current_words += sentence_words + + continue + + # ---------------------------------------------------- + # Finish current chunk + # ---------------------------------------------------- + + if current_sentences: + chunks.append( + " ".join( + current_sentences + ) + ) + + # ---------------------------------------------------- + # Add limited overlap + # ---------------------------------------------------- + + overlap = get_overlap_sentences( + current_sentences + ) + + # Make sure overlap + new sentence does not exceed + # the maximum chunk size. + while ( + overlap + and ( + sum( + word_count(item) + for item in overlap + ) + + sentence_words + > MAX_CHUNK_WORDS + ) + ): + overlap.pop(0) + + current_sentences = ( + overlap + [sentence] + ) + + current_words = sum( + word_count(item) + for item in current_sentences + ) + + # Final chunk. + if current_sentences: + chunks.append( + " ".join( + current_sentences + ) + ) + + return chunks + + +# ============================================================ +# Main ingestion +# ============================================================ + +def ingest_pdf( + pdf_path: Path, +) -> None: + + # -------------------------------------------------------- + # Validate file + # -------------------------------------------------------- + + if not pdf_path.exists(): + raise FileNotFoundError( + f"PDF not found: {pdf_path}" + ) + + if pdf_path.suffix.lower() != ".pdf": + raise ValueError( + f"Expected a PDF file, got: {pdf_path}" + ) + + print(f"PDF: {pdf_path}") + + # -------------------------------------------------------- + # Extract PDF + # -------------------------------------------------------- + + print("Extracting PDF text...") + + pages = extract_pages( + pdf_path + ) + + if not pages: + raise RuntimeError( + "No text could be extracted from the PDF." + ) + + print( + f"Extracted text from " + f"{len(pages)} pages." + ) + + # -------------------------------------------------------- + # Create stable paper ID + # -------------------------------------------------------- + + paper_id = generate_paper_id( + pdf_path + ) + + print( + f"Paper ID: " + f"{paper_id[:16]}..." + ) + + # -------------------------------------------------------- + # Create chunks + # -------------------------------------------------------- + + print("Creating chunks...") + + documents = [] + + chunk_index = 0 + + for page in pages: + + chunks = create_chunks( + page["text"] + ) + + for chunk in chunks: + + documents.append( + { + "paper_id": paper_id, + "source": pdf_path.name, + "page": page["page"], + "chunk_index": chunk_index, + + # This is the field indexed by the + # Atlas autoEmbed Vector Search index. + "content": chunk, + } + ) + + chunk_index += 1 + + if not documents: + raise RuntimeError( + "No chunks were generated." + ) + + print( + f"Created {len(documents)} chunks." + ) + + # -------------------------------------------------------- + # Connect to MongoDB + # -------------------------------------------------------- + + print("Connecting to MongoDB...") + + client = MongoClient( + MONGODB_URI, + serverSelectionTimeoutMS=10000, + ) + + try: + # Verify connection. + client.admin.command( + "ping" + ) + + database = client[ + MONGODB_DATABASE + ] + + collection = database[ + MONGODB_COLLECTION + ] + + print( + "MongoDB connection successful." + ) + + # ---------------------------------------------------- + # Regular MongoDB index + # ---------------------------------------------------- + + collection.create_index( + [ + ("paper_id", 1), + ("chunk_index", 1), + ], + unique=True, + ) + + # ---------------------------------------------------- + # Upsert chunks + # ---------------------------------------------------- + + print("Uploading chunks...") + + operations = [] + + for document in documents: + + operations.append( + ReplaceOne( + { + "paper_id": + document["paper_id"], + "chunk_index": + document["chunk_index"], + }, + document, + upsert=True, + ) + ) + + result = collection.bulk_write( + operations, + ordered=False, + ) + + # ---------------------------------------------------- + # Remove obsolete chunks + # ---------------------------------------------------- + # + # Useful if the same PDF was previously ingested with + # different chunk settings. + + delete_result = collection.delete_many( + { + "paper_id": paper_id, + "chunk_index": { + "$gte": len(documents) + }, + } + ) + + # ---------------------------------------------------- + # Summary + # ---------------------------------------------------- + + print() + print("=" * 70) + print("INGESTION COMPLETE") + print("=" * 70) + + print( + f"PDF: {pdf_path.name}" + ) + + print( + f"Pages: {len(pages)}" + ) + + print( + f"Chunks: {len(documents)}" + ) + + print( + f"Inserted: " + f"{result.upserted_count}" + ) + + print( + f"Updated: " + f"{result.modified_count}" + ) + + print( + f"Deleted: " + f"{delete_result.deleted_count}" + ) + + print() + print( + "Embeddings are handled automatically " + "by the MongoDB Vector Search index." + ) + + finally: + client.close() + + +# ============================================================ +# CLI +# ============================================================ + +if __name__ == "__main__": + + if len(sys.argv) != 2: + + print( + "Usage:\n" + " uv run ingest.py " + "data/paper.pdf" + ) + + sys.exit(1) + + path = Path( + sys.argv[1] + ) + + ingest_pdf( + path + ) \ No newline at end of file diff --git a/RAG/rag-ingestion/ingest_all.py b/RAG/rag-ingestion/ingest_all.py new file mode 100644 index 0000000..769416f --- /dev/null +++ b/RAG/rag-ingestion/ingest_all.py @@ -0,0 +1,136 @@ +import subprocess +import sys +from pathlib import Path + + +DATA_FOLDER = Path("data") +INGEST_SCRIPT = Path("ingest.py") + + +def get_pdf_files() -> list[Path]: + """ + Return all PDF files inside the data folder, + sorted alphabetically. + """ + + if not DATA_FOLDER.exists(): + raise FileNotFoundError( + f"Data folder not found: {DATA_FOLDER.resolve()}" + ) + + return sorted( + file + for file in DATA_FOLDER.iterdir() + if file.is_file() + and file.suffix.lower() == ".pdf" + ) + + +def ingest_pdf(pdf_path: Path) -> bool: + """ + Run ingest.py for one PDF. + + Returns True if ingestion succeeds, + otherwise False. + """ + + print() + print("=" * 70) + print(f"Ingesting: {pdf_path.name}") + print("=" * 70) + + result = subprocess.run( + [ + sys.executable, + str(INGEST_SCRIPT), + str(pdf_path), + ] + ) + + if result.returncode == 0: + print() + print(f"✓ Successfully ingested: {pdf_path.name}") + return True + + print() + print(f"✗ Failed to ingest: {pdf_path.name}") + + return False + + +def main() -> None: + + if not INGEST_SCRIPT.exists(): + print( + f"Could not find ingest script: " + f"{INGEST_SCRIPT.resolve()}" + ) + sys.exit(1) + + try: + pdf_files = get_pdf_files() + + except FileNotFoundError as error: + print(error) + sys.exit(1) + + if not pdf_files: + print( + f"No PDF files found in " + f"{DATA_FOLDER.resolve()}" + ) + return + + print( + f"Found {len(pdf_files)} PDF file(s):" + ) + + for pdf in pdf_files: + print(f" - {pdf.name}") + + successful = [] + failed = [] + + for pdf in pdf_files: + + if ingest_pdf(pdf): + successful.append(pdf.name) + else: + failed.append(pdf.name) + + # -------------------------------------------------------- + # Summary + # -------------------------------------------------------- + + print() + print("=" * 70) + print("INGESTION SUMMARY") + print("=" * 70) + + print( + f"Total: {len(pdf_files)}" + ) + + print( + f"Successful: {len(successful)}" + ) + + print( + f"Failed: {len(failed)}" + ) + + if failed: + print() + print("Failed files:") + + for filename in failed: + print(f" - {filename}") + + sys.exit(1) + + print() + print("All papers successfully ingested.") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/RAG/rag-ingestion/ingest_withmodel.py b/RAG/rag-ingestion/ingest_withmodel.py new file mode 100644 index 0000000..bd9bb5f --- /dev/null +++ b/RAG/rag-ingestion/ingest_withmodel.py @@ -0,0 +1,567 @@ +import hashlib +import os +import re +import sys +from pathlib import Path + +from dotenv import load_dotenv +from pypdf import PdfReader +from pymongo import MongoClient, ReplaceOne +from sentence_transformers import SentenceTransformer + + +# ============================================================ +# Configuration +# ============================================================ + +MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2" + +# MiniLM supports up to ~256 tokens. +# Keep some margin below that limit. +MAX_CHUNK_TOKENS = 200 +OVERLAP_TOKENS = 30 + +EMBEDDING_DIMENSIONS = 384 +EMBEDDING_BATCH_SIZE = 32 + +load_dotenv() + +MONGODB_URI = os.environ["MONGO_URI"] +MONGODB_DATABASE = os.environ.get("MONGO_DB_NAME", "rag_db_test") +MONGODB_COLLECTION = os.environ.get( + "MONGO_DB_COLLECTION_NAME", + "test_paper_chunks", +) + + +# ============================================================ +# Helpers +# ============================================================ + +def generate_paper_id(pdf_path: Path) -> str: + """ + Generate a stable ID based on the PDF contents. + + If the exact same PDF is ingested again, it receives + the same paper_id. + """ + sha256 = hashlib.sha256() + + with pdf_path.open("rb") as file: + while chunk := file.read(1024 * 1024): + sha256.update(chunk) + + return sha256.hexdigest() + + +def clean_text(text: str) -> str: + """ + Clean common PDF extraction artifacts without heavily + modifying the source text. + """ + + # Remove null bytes + text = text.replace("\x00", "") + + # Join words broken across lines: + # + # "transfor-\nmation" + # becomes + # "transformation" + text = re.sub( + r"(?<=\w)-\s*\n\s*(?=\w)", + "", + text, + ) + + # Normalize line breaks inside paragraphs + text = re.sub( + r"(? list[dict]: + """ + Extract text from each PDF page independently so that + page metadata can be retained in MongoDB. + """ + + reader = PdfReader(pdf_path) + + pages = [] + + for page_number, page in enumerate( + reader.pages, + start=1, + ): + raw_text = page.extract_text() + + if not raw_text: + continue + + text = clean_text(raw_text) + + if not text: + continue + + pages.append( + { + "page": page_number, + "text": text, + } + ) + + return pages + + +# ============================================================ +# Chunking +# ============================================================ + +def split_into_sentences(text: str) -> list[str]: + """ + Basic sentence splitter. + + This keeps chunks more natural than blindly splitting + every N tokens. + """ + + sentences = re.split( + r"(?<=[.!?])\s+", + text, + ) + + return [ + sentence.strip() + for sentence in sentences + if sentence.strip() + ] + + +def token_count( + text: str, + tokenizer, +) -> int: + return len( + tokenizer.encode( + text, + add_special_tokens=False, + ) + ) + + +def split_long_sentence( + sentence: str, + tokenizer, +) -> list[str]: + """ + Fallback for a sentence that by itself exceeds the + maximum chunk size. + """ + + token_ids = tokenizer.encode( + sentence, + add_special_tokens=False, + ) + + pieces = [] + + start = 0 + + while start < len(token_ids): + end = min( + start + MAX_CHUNK_TOKENS, + len(token_ids), + ) + + piece = tokenizer.decode( + token_ids[start:end], + skip_special_tokens=True, + ).strip() + + if piece: + pieces.append(piece) + + if end >= len(token_ids): + break + + start = end - OVERLAP_TOKENS + + return pieces + + +def get_overlap_sentences( + sentences: list[str], + tokenizer, +) -> list[str]: + """ + Preserve the final ~OVERLAP_TOKENS worth of sentences + for the next chunk. + """ + + overlap = [] + total_tokens = 0 + + for sentence in reversed(sentences): + sentence_tokens = token_count( + sentence, + tokenizer, + ) + + if ( + total_tokens + sentence_tokens + > OVERLAP_TOKENS + ): + break + + overlap.insert(0, sentence) + total_tokens += sentence_tokens + + return overlap + + +def create_chunks( + text: str, + tokenizer, +) -> list[str]: + """ + Create sentence-aware chunks while respecting the model's + token limit. + """ + + sentences = split_into_sentences(text) + + chunks = [] + current_sentences = [] + current_tokens = 0 + + for sentence in sentences: + + sentence_tokens = token_count( + sentence, + tokenizer, + ) + + # Handle unusually long individual sentences + if sentence_tokens > MAX_CHUNK_TOKENS: + + if current_sentences: + chunks.append( + " ".join(current_sentences) + ) + + current_sentences = [] + current_tokens = 0 + + chunks.extend( + split_long_sentence( + sentence, + tokenizer, + ) + ) + + continue + + # Sentence still fits in current chunk + if ( + current_tokens + sentence_tokens + <= MAX_CHUNK_TOKENS + ): + current_sentences.append(sentence) + current_tokens += sentence_tokens + continue + + # Finish the existing chunk + chunks.append( + " ".join(current_sentences) + ) + + # Keep some semantic overlap with the next chunk + overlap = get_overlap_sentences( + current_sentences, + tokenizer, + ) + + current_sentences = overlap + [sentence] + + current_tokens = sum( + token_count(item, tokenizer) + for item in current_sentences + ) + + if current_sentences: + chunks.append( + " ".join(current_sentences) + ) + + return chunks + + +# ============================================================ +# Main ingestion +# ============================================================ + +def ingest_pdf(pdf_path: Path) -> None: + + if not pdf_path.exists(): + raise FileNotFoundError( + f"PDF not found: {pdf_path}" + ) + + if pdf_path.suffix.lower() != ".pdf": + raise ValueError( + f"Expected a PDF file, got: {pdf_path}" + ) + + print(f"PDF: {pdf_path}") + + # -------------------------------------------------------- + # Load embedding model + # -------------------------------------------------------- + + print(f"Loading model: {MODEL_NAME}") + + model = SentenceTransformer( + MODEL_NAME + ) + + tokenizer = model.tokenizer + + # -------------------------------------------------------- + # Extract PDF + # -------------------------------------------------------- + + print("Extracting PDF text...") + + pages = extract_pages(pdf_path) + + if not pages: + raise RuntimeError( + "No text could be extracted from the PDF." + ) + + print( + f"Extracted text from {len(pages)} pages." + ) + + # -------------------------------------------------------- + # Create stable paper ID + # -------------------------------------------------------- + + paper_id = generate_paper_id(pdf_path) + + print( + f"Paper ID: {paper_id[:16]}..." + ) + + # -------------------------------------------------------- + # Create chunks + # -------------------------------------------------------- + + documents = [] + + chunk_index = 0 + + for page in pages: + + chunks = create_chunks( + page["text"], + tokenizer, + ) + + for chunk in chunks: + + documents.append( + { + "paper_id": paper_id, + "source": pdf_path.name, + "page": page["page"], + "chunk_index": chunk_index, + "content": chunk, + } + ) + + chunk_index += 1 + + if not documents: + raise RuntimeError( + "No chunks were generated." + ) + + print( + f"Created {len(documents)} chunks." + ) + + # -------------------------------------------------------- + # Generate embeddings + # -------------------------------------------------------- + + print("Generating embeddings...") + + texts = [ + document["content"] + for document in documents + ] + + embeddings = model.encode( + texts, + batch_size=EMBEDDING_BATCH_SIZE, + show_progress_bar=True, + normalize_embeddings=True, + convert_to_numpy=True, + ) + + if embeddings.shape[1] != EMBEDDING_DIMENSIONS: + raise RuntimeError( + "Unexpected embedding dimension: " + f"{embeddings.shape[1]}" + ) + + # -------------------------------------------------------- + # Attach embeddings + metadata + # -------------------------------------------------------- + + for document, embedding in zip( + documents, + embeddings, + ): + document["embedding"] = ( + embedding.tolist() + ) + + document["embedding_model"] = ( + MODEL_NAME + ) + + document["embedding_dimensions"] = ( + EMBEDDING_DIMENSIONS + ) + + # -------------------------------------------------------- + # Connect to MongoDB + # -------------------------------------------------------- + + print("Connecting to MongoDB...") + + client = MongoClient( + MONGODB_URI, + serverSelectionTimeoutMS=10000, + ) + + # Verify connection + client.admin.command("ping") + + database = client[ + MONGODB_DATABASE + ] + + collection = database[ + MONGODB_COLLECTION + ] + + print("MongoDB connection successful.") + + # -------------------------------------------------------- + # Useful regular MongoDB index + # -------------------------------------------------------- + + collection.create_index( + [ + ("paper_id", 1), + ("chunk_index", 1), + ], + unique=True, + ) + + # -------------------------------------------------------- + # Upsert chunks + # -------------------------------------------------------- + + print("Uploading chunks...") + + operations = [] + + for document in documents: + + operations.append( + ReplaceOne( + { + "paper_id": document["paper_id"], + "chunk_index": document["chunk_index"], + }, + document, + upsert=True, + ) + ) + + result = collection.bulk_write( + operations, + ordered=False, + ) + + # Remove obsolete chunks in case this document had + # previously been ingested with different chunk settings. + collection.delete_many( + { + "paper_id": paper_id, + "chunk_index": { + "$gte": len(documents) + }, + } + ) + + print() + print("Ingestion complete.") + print(f"PDF: {pdf_path.name}") + print(f"Pages: {len(pages)}") + print(f"Chunks: {len(documents)}") + print( + f"Inserted: {result.upserted_count}" + ) + print( + f"Updated: {result.modified_count}" + ) + print( + f"Embedding dimensions: " + f"{EMBEDDING_DIMENSIONS}" + ) + + client.close() + + +# ============================================================ +# CLI +# ============================================================ + +if __name__ == "__main__": + + if len(sys.argv) != 2: + print( + "Usage:\n" + " uv run ingest.py " + "data/paper.pdf" + ) + + sys.exit(1) + + path = Path(sys.argv[1]) + + ingest_pdf(path) \ No newline at end of file diff --git a/RAG/rag-ingestion/original_data/001_Petersen.pdf b/RAG/rag-ingestion/original_data/001_Petersen.pdf new file mode 100644 index 0000000..280be87 Binary files /dev/null and b/RAG/rag-ingestion/original_data/001_Petersen.pdf differ diff --git a/RAG/rag-ingestion/original_data/1-s2.0-S0950584908001390-main.pdf b/RAG/rag-ingestion/original_data/1-s2.0-S0950584908001390-main.pdf new file mode 100644 index 0000000..f69e221 Binary files /dev/null and b/RAG/rag-ingestion/original_data/1-s2.0-S0950584908001390-main.pdf differ diff --git a/RAG/rag-ingestion/original_data/Evidence_based_software_engineering.pdf b/RAG/rag-ingestion/original_data/Evidence_based_software_engineering.pdf new file mode 100644 index 0000000..a09398a Binary files /dev/null and b/RAG/rag-ingestion/original_data/Evidence_based_software_engineering.pdf differ diff --git "a/RAG/rag-ingestion/original_data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" "b/RAG/rag-ingestion/original_data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" new file mode 100644 index 0000000..6fddd9d Binary files /dev/null and "b/RAG/rag-ingestion/original_data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" differ diff --git a/RAG/rag-ingestion/original_data/Kitchenham-2007Systematicreviews5-8.pdf b/RAG/rag-ingestion/original_data/Kitchenham-2007Systematicreviews5-8.pdf new file mode 100644 index 0000000..ee87046 Binary files /dev/null and b/RAG/rag-ingestion/original_data/Kitchenham-2007Systematicreviews5-8.pdf differ diff --git a/RAG/rag-ingestion/original_data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf b/RAG/rag-ingestion/original_data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf new file mode 100644 index 0000000..4972fb7 Binary files /dev/null and b/RAG/rag-ingestion/original_data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf differ diff --git a/RAG/rag-ingestion/original_data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf b/RAG/rag-ingestion/original_data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf new file mode 100644 index 0000000..c7a965b Binary files /dev/null and b/RAG/rag-ingestion/original_data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf differ diff --git a/RAG/rag-ingestion/original_data/Rapid Reviews in Software Engineering.pdf b/RAG/rag-ingestion/original_data/Rapid Reviews in Software Engineering.pdf new file mode 100644 index 0000000..f527880 Binary files /dev/null and b/RAG/rag-ingestion/original_data/Rapid Reviews in Software Engineering.pdf differ diff --git a/RAG/rag-ingestion/original_data/SLR Student Guide.pdf b/RAG/rag-ingestion/original_data/SLR Student Guide.pdf new file mode 100644 index 0000000..c92c656 Binary files /dev/null and b/RAG/rag-ingestion/original_data/SLR Student Guide.pdf differ diff --git a/RAG/rag-ingestion/original_data/Synthesizing Evidence in Software Engineering Research.pdf b/RAG/rag-ingestion/original_data/Synthesizing Evidence in Software Engineering Research.pdf new file mode 100644 index 0000000..e720f94 Binary files /dev/null and b/RAG/rag-ingestion/original_data/Synthesizing Evidence in Software Engineering Research.pdf differ diff --git "a/RAG/rag-ingestion/original_data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" "b/RAG/rag-ingestion/original_data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" new file mode 100644 index 0000000..2c153fd Binary files /dev/null and "b/RAG/rag-ingestion/original_data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" differ diff --git a/RAG/rag-ingestion/original_data/pizard_2024.pdf b/RAG/rag-ingestion/original_data/pizard_2024.pdf new file mode 100644 index 0000000..0c8d28a Binary files /dev/null and b/RAG/rag-ingestion/original_data/pizard_2024.pdf differ diff --git a/RAG/rag-ingestion/pyproject.toml b/RAG/rag-ingestion/pyproject.toml new file mode 100644 index 0000000..b1f3b91 --- /dev/null +++ b/RAG/rag-ingestion/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "rag-ingestion" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "pymongo>=4.18.1", + "pypdf>=6.18.1", + "python-dotenv>=1.2.3", + "sentence-transformers>=6.0.1", +] diff --git a/RAG/rag-ingestion/test_search.py b/RAG/rag-ingestion/test_search.py new file mode 100644 index 0000000..c5cebfa --- /dev/null +++ b/RAG/rag-ingestion/test_search.py @@ -0,0 +1,268 @@ +import os +import sys + +from dotenv import load_dotenv +from pymongo import MongoClient + + +# ============================================================ +# Configuration +# ============================================================ + +load_dotenv() + +MONGODB_URI = os.environ["MONGO_URI"] + +MONGODB_DATABASE = os.environ.get( + "MONGO_DB_NAME" +) + +MONGODB_COLLECTION = os.environ.get( + "MONGO_DB_RAG_COLLECTION_NAME", +) + +# IMPORTANT: +# Change this if your Atlas Vector Search index has +# a different name. +VECTOR_SEARCH_INDEX = os.environ.get( + "VECTOR_SEARCH_INDEX", + "autoembed_index", +) + +DEFAULT_LIMIT = 5 + + +# ============================================================ +# Vector Search +# ============================================================ + +def search( + query: str, + limit: int = DEFAULT_LIMIT, +) -> list[dict]: + """ + Send a plain-text query to MongoDB Atlas. + + Atlas automatically generates the query embedding using + the embedding model configured in the Vector Search index + and returns the most semantically similar chunks. + """ + + client = MongoClient( + MONGODB_URI, + serverSelectionTimeoutMS=10000, + ) + + try: + # Verify connection. + client.admin.command( + "ping" + ) + + database = client[ + MONGODB_DATABASE + ] + + collection = database[ + MONGODB_COLLECTION + ] + + pipeline = [ + { + "$vectorSearch": { + "index": + VECTOR_SEARCH_INDEX, + + # Same field configured as autoEmbed + # in the Vector Search index. + "path": + "content", + + # Plain text. + # + # NO local embedding generation. + # MongoDB embeds this automatically. + "query": { + "text": query + }, + + # Number of candidate vectors Atlas + # considers before returning the best + # matches. + "numCandidates": + max(limit * 10, 50), + + "limit": + limit, + } + }, + { + "$project": { + "_id": 0, + + "paper_id": 1, + "source": 1, + "page": 1, + "chunk_index": 1, + "content": 1, + + "score": { + "$meta": + "vectorSearchScore" + }, + } + }, + ] + + return list( + collection.aggregate( + pipeline + ) + ) + + finally: + client.close() + + +# ============================================================ +# Result display +# ============================================================ + +def print_results( + query: str, + results: list[dict], +) -> None: + + print() + print("=" * 80) + print("QUERY") + print("=" * 80) + print(query) + + if not results: + + print() + print( + "No results found." + ) + + return + + print() + print( + f"Returned " + f"{len(results)} result(s)." + ) + + for index, result in enumerate( + results, + start=1, + ): + + print() + print("=" * 80) + print( + f"RESULT {index}" + ) + print("=" * 80) + + score = result.get( + "score" + ) + + if isinstance( + score, + (int, float), + ): + print( + f"Score: {score:.6f}" + ) + + else: + print( + f"Score: {score}" + ) + + print( + f"Source: " + f"{result.get('source', 'N/A')}" + ) + + print( + f"Page: " + f"{result.get('page', 'N/A')}" + ) + + print( + f"Chunk: " + f"{result.get('chunk_index', 'N/A')}" + ) + + print() + print( + result.get( + "content", + "", + ) + ) + + +# ============================================================ +# CLI +# ============================================================ + +def main() -> None: + + # Query supplied directly: + # + # uv run test_search.py "your question" + + if len(sys.argv) > 1: + + query = " ".join( + sys.argv[1:] + ).strip() + + # Or interactive mode: + # + # uv run test_search.py + + else: + + query = input( + "Enter search query: " + ).strip() + + if not query: + + print( + "Query cannot be empty." + ) + + sys.exit(1) + + try: + results = search( + query=query, + limit=DEFAULT_LIMIT, + ) + + except Exception as error: + + print() + print( + "Vector search failed:" + ) + + print(error) + + sys.exit(1) + + print_results( + query, + results, + ) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/RAG/rag-ingestion/test_search_withmodel.py b/RAG/rag-ingestion/test_search_withmodel.py new file mode 100644 index 0000000..c79be70 --- /dev/null +++ b/RAG/rag-ingestion/test_search_withmodel.py @@ -0,0 +1,269 @@ +import argparse +import os +import sys + +from dotenv import load_dotenv +from pymongo import MongoClient +from sentence_transformers import SentenceTransformer + + +# ============================================================ +# Configuration +# ============================================================ + +MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2" +EMBEDDING_DIMENSIONS = 384 + +VECTOR_INDEX_NAME = "paper_vector_index" +VECTOR_FIELD = "embedding" + +DEFAULT_LIMIT = 5 + + +# ============================================================ +# Environment +# ============================================================ + +load_dotenv() + +MONGODB_URI = os.environ["MONGO_URI"] +MONGODB_DATABASE = os.environ.get( + "MONGO_DB_NAME", + "rag_db", +) +MONGODB_COLLECTION = os.environ.get( + "MONGO_DB_COLLECTION_NAME", + "rag_paper_chunks", +) + + +# ============================================================ +# Search +# ============================================================ + +def search( + query: str, + limit: int = DEFAULT_LIMIT, +) -> list[dict]: + + print(f'Query: "{query}"') + print() + + # -------------------------------------------------------- + # Load same embedding model used for ingestion + # -------------------------------------------------------- + + print(f"Loading model: {MODEL_NAME}") + + model = SentenceTransformer( + MODEL_NAME + ) + + # -------------------------------------------------------- + # Generate query embedding + # -------------------------------------------------------- + + print("Generating query embedding...") + + query_embedding = model.encode( + query, + normalize_embeddings=True, + convert_to_numpy=True, + ) + + if len(query_embedding) != EMBEDDING_DIMENSIONS: + raise RuntimeError( + "Unexpected embedding dimension: " + f"{len(query_embedding)}" + ) + + query_vector = query_embedding.tolist() + + # -------------------------------------------------------- + # MongoDB connection + # -------------------------------------------------------- + + print("Connecting to MongoDB...") + + client = MongoClient( + MONGODB_URI, + serverSelectionTimeoutMS=10000, + ) + + try: + client.admin.command("ping") + + collection = client[ + MONGODB_DATABASE + ][ + MONGODB_COLLECTION + ] + + print("MongoDB connection successful.") + + # MongoDB recommends considering substantially more + # candidates than the final number of results. + num_candidates = max( + limit * 20, + 100, + ) + + # ---------------------------------------------------- + # Vector Search + # ---------------------------------------------------- + + pipeline = [ + { + "$vectorSearch": { + "index": VECTOR_INDEX_NAME, + "path": VECTOR_FIELD, + "queryVector": query_vector, + "numCandidates": num_candidates, + "limit": limit, + } + }, + { + "$project": { + "_id": 0, + "paper_id": 1, + "source": 1, + "page": 1, + "chunk_index": 1, + "content": 1, + "score": { + "$meta": "vectorSearchScore" + }, + } + }, + ] + + return list( + collection.aggregate(pipeline) + ) + + finally: + client.close() + + +# ============================================================ +# Output +# ============================================================ + +def print_results(results: list[dict]) -> None: + + if not results: + print() + print("No results found.") + print( + "Make sure paper_vector_index is READY " + "and that documents have been ingested." + ) + return + + print() + print("=" * 70) + print(f"FOUND {len(results)} RESULTS") + print("=" * 70) + + for position, result in enumerate( + results, + start=1, + ): + print() + print(f"RESULT #{position}") + print("-" * 70) + + print( + f"Score: " + f"{result.get('score', 0):.4f}" + ) + + print( + f"Source: " + f"{result.get('source', 'unknown')}" + ) + + print( + f"Page: " + f"{result.get('page', 'unknown')}" + ) + + print( + f"Chunk: " + f"{result.get('chunk_index', 'unknown')}" + ) + + print() + print(result.get("content", "")) + + print() + print("-" * 70) + + +# ============================================================ +# CLI +# ============================================================ + +def main(): + + parser = argparse.ArgumentParser( + description=( + "Test semantic search against " + "MongoDB Vector Search." + ) + ) + + parser.add_argument( + "query", + nargs="*", + help="Natural-language query", + ) + + parser.add_argument( + "--limit", + type=int, + default=DEFAULT_LIMIT, + help="Number of results to return (default: 5)", + ) + + args = parser.parse_args() + + # Allow either: + # + # uv run test_search.py "some query" + # + # or: + # + # uv run test_search.py + # Query: ... + + if args.query: + query = " ".join(args.query) + else: + query = input("Query: ").strip() + + if not query: + print("Query cannot be empty.") + sys.exit(1) + + if args.limit < 1: + print("--limit must be at least 1.") + sys.exit(1) + + try: + results = search( + query=query, + limit=args.limit, + ) + + print_results(results) + + except Exception as error: + print() + print("Search failed:") + print(error) + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/RAG/rag-ingestion/uv.lock b/RAG/rag-ingestion/uv.lock new file mode 100644 index 0000000..80af0ac --- /dev/null +++ b/RAG/rag-ingestion/uv.lock @@ -0,0 +1,1049 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cuda-bindings" +version = "13.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/17/74346b49114779920929ec0ea1361f0a0357262d6020cfdd017f670da638/cuda_bindings-13.4.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7df2dddb81feb15787e8c4a13b7aa3f4c23eabeec7746037e94dbbed065fc6c", size = 6406821, upload-time = "2026-09-10T01:17:12.344Z" }, + { url = "https://files.pythonhosted.org/packages/f6/77/2f9a38be7399a34e3703b6ce1be60bb2c56611d324750f8d544ff90b0473/cuda_bindings-13.4.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62df23df11074e9833bf348bbcf0b8eec2fcbded4f305c6fbaa3ed067433e97d", size = 6972525, upload-time = "2026-09-10T01:17:15.098Z" }, + { url = "https://files.pythonhosted.org/packages/7d/de/41197aebf91f6c5f82b35e06e3b4bbe08edddb335c4d6e53c1f1fe542e0f/cuda_bindings-13.4.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0947a6c491a622b076e9afcbc0444eefd9cbe001630558bbcba8d0a90dab7fe7", size = 6243282, upload-time = "2026-09-10T01:17:21.158Z" }, + { url = "https://files.pythonhosted.org/packages/be/d6/1b697092f53cfd4d721fbbe13e7d66dc72a9d508156af9be4c86607e4373/cuda_bindings-13.4.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e730e80d997b6037566033a79aae3995e1981654054536f512e5c091679c70", size = 6803245, upload-time = "2026-09-10T01:17:23.309Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/e6/22df83f82f9bc26cb1c42265cf14d34d4908dba2a0f261bd7b28244acb00/cuda_pathfinder-1.8.1-py3-none-any.whl", hash = "sha256:ae0137ff9e56ea97499bcbf54f5f2778ec25f3266715ac86da192a795af982a8", size = 62552, upload-time = "2026-09-02T16:55:28.64Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +curand = [ + { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cusolver = [ + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/46/126b1831dca12060d4a8296bf9c4fe5c93c4f22197fa239cb0cc82042bba/filelock-3.32.6.tar.gz", hash = "sha256:a3f55a18af3652a94d8f47d6055df434f254ca1d02ef2524850c6d249ca2512c", size = 225172, upload-time = "2026-09-08T22:57:11.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/06/4f138f618dbea66803291274f228f01daf29f306fe8b96bc30dab765df75/filelock-3.32.6-py3-none-any.whl", hash = "sha256:3f16ecd0117feae0dfc147e8c62eb5daeccd8bd800378c3ddf416de9b4feb6b1", size = 100189, upload-time = "2026-09-08T22:57:10.182Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527, upload-time = "2026-08-03T22:33:13.243Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/62/3c062f593bd92ef4e77a0ef39541e3d82a0a1d3947c8a777a02a13a27828/hf_xet-1.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:70cbb9c896901600128cb9b6f06e132954fbede1db30f31f7c6c63f84cb7c31d", size = 4074584, upload-time = "2026-08-03T22:32:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1e/c0ad437dd267a8e435bef594acf781bbc3874ff0b6435b4962d03ecf7cc4/hf_xet-1.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23379c2f9ec8696d952b16414a2bae72cad86a52df869b050698ba60f538c675", size = 3867381, upload-time = "2026-08-03T22:32:49.049Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ee/7c0d7b6ab336167531b1c30af2af003f054af4c749becbd7209ae33a77c3/hf_xet-1.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f7278c05c22fd60cb436cda1269649b3e81db65ecdc8496e5e164aa4143e7b", size = 4453982, upload-time = "2026-08-03T22:32:50.568Z" }, + { url = "https://files.pythonhosted.org/packages/63/06/ad8eab1c9525246650cbaa821caa3cdbaca734ab1a5b8c91bea09cbd8d69/hf_xet-1.6.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:948f15d3a9545cfe5932f6bd8b440f6ae630aee108f14b7bd6c561f7c2dcc522", size = 4249445, upload-time = "2026-08-03T22:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/d8/26/1eee8aedb0dafc1ab9717dc9ac602cde33361b232dc06803f1f6ed18b58c/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5153e6bb103ad49d6ea9f1b2e230db5a2ea32551ad09a706d2f61d7c7c80d80e", size = 4451099, upload-time = "2026-08-03T22:32:54.114Z" }, + { url = "https://files.pythonhosted.org/packages/67/57/0b88af1f194ab6c9c650547d9cc06bfeaab836ae4dcdb331676bfb8be95a/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:35cec30d75c6f9eb9c16a77cef68e85a103b72e24d4b473714ec9ff06428bab9", size = 4664712, upload-time = "2026-08-03T22:32:55.547Z" }, + { url = "https://files.pythonhosted.org/packages/53/a0/26b717a9d1840e8abf48dcec64b5ed8fbe472671d38ad28d30e147132b33/hf_xet-1.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5789835d7c6bc9436962853192082374297fb72d7eff7e7762ec25ceb7e25338", size = 4025906, upload-time = "2026-08-03T22:32:57.391Z" }, + { url = "https://files.pythonhosted.org/packages/49/f6/4a9966633c6fef83af997e2cff68ec1963676d412bdfd096df2a93b8e185/hf_xet-1.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:75765820ce4700db3750c94acc8fe27c5fae4c9ec000a0dbac3ca082acf97765", size = 3849221, upload-time = "2026-08-03T22:32:59.123Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729, upload-time = "2026-08-03T22:33:00.721Z" }, + { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287, upload-time = "2026-08-03T22:33:02.251Z" }, + { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663, upload-time = "2026-08-03T22:33:03.802Z" }, + { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538, upload-time = "2026-08-03T22:33:05.287Z" }, + { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520, upload-time = "2026-08-03T22:33:06.81Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937, upload-time = "2026-08-03T22:33:08.616Z" }, + { url = "https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b", size = 4033128, upload-time = "2026-08-03T22:33:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3", size = 3859359, upload-time = "2026-08-03T22:33:11.725Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/f0/61159db90b5cd275d55516fe27920828e7d3be4053fdbdb27c3f70e5f1ef/huggingface_hub-1.31.0.tar.gz", hash = "sha256:f8e9e710a210613fa5d0f26bba6da05ef4aef9fba5a0f23f508f5ac4d08b6f90", size = 968039, upload-time = "2026-09-10T10:27:22.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/7f/3f886a625043b77312b80da2f2bf00b5ecbf5a73061af1aa0259cd258c9d/huggingface_hub-1.31.0-py3-none-any.whl", hash = "sha256:9dbb6a503cbe2494ea666695207e7262d410659e09134059deb83e5480864667", size = 798313, upload-time = "2026-09-10T10:27:20.798Z" }, +] + +[[package]] +name = "idna" +version = "3.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/1d/537ab090f302b838943a1b56497dd53059b9a9b46a074936470173a2e207/joblib-1.6.0.tar.gz", hash = "sha256:2ccc96785b12046c08fd6d55839c12857831b54a3c1673ffadd2f04bfc4eda03", size = 327903, upload-time = "2026-08-31T09:39:04.122Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/53/84099323c2ec4be98d935f63c033ac4151ee83836ca1050ede3b3aadf155/joblib-1.6.0-py3-none-any.whl", hash = "sha256:3dbbf9f6e4b592a2357b854608e980fe6390d131d7a82f011a377ef2ebef7aba", size = 306115, upload-time = "2026-08-31T09:39:02.298Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "narwhals" +version = "2.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/68/5351e34623d253423240ea7de3f8fc74fa8ab14b1ab3c0ec4ac8997413c9/narwhals-2.26.0.tar.gz", hash = "sha256:6b9cadca82f375c7e4cf584fdc86ca25da54827307a9c58f94547ee6104b82dd", size = 686970, upload-time = "2026-09-08T13:32:08.964Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b5/1b84b2c784db76d69442334bc8b8748c840f13ca53be086f4f250ad4a0bc/narwhals-2.26.0-py3-none-any.whl", hash = "sha256:29326d74f107c347fd1009bd58e38d9f7c7c5b51e6de97bc93dbc325d9038b54", size = 474034, upload-time = "2026-09-08T13:32:07.159Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/78/cf416f15dc29375a229d9dfebf8db6e313f291580b39fa1a568b6052bb07/numpy-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a", size = 16998686, upload-time = "2026-09-06T16:25:33.171Z" }, + { url = "https://files.pythonhosted.org/packages/9e/59/abcc2d8def4fd60eec7d87f92d27c13448ffd9ab14339bcc63a0d7a2fdea/numpy-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c", size = 12013862, upload-time = "2026-09-06T16:25:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/94/75/4640d2d6e4b64a049e48425a82728a41ef4adb61332d2cba68055774878b/numpy-2.5.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07", size = 5449793, upload-time = "2026-09-06T16:25:39.476Z" }, + { url = "https://files.pythonhosted.org/packages/96/cd/625b57ae33d4ca560f32cc0b47b4a5922146d9beb998ddf773900d440a73/numpy-2.5.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0", size = 6785176, upload-time = "2026-09-06T16:25:42.069Z" }, + { url = "https://files.pythonhosted.org/packages/9c/72/12918652e7912ef9751e8694c88820fcd1908e0618cb23f5f3caa6004b7b/numpy-2.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc", size = 15703377, upload-time = "2026-09-06T16:25:45.135Z" }, + { url = "https://files.pythonhosted.org/packages/45/8f/9beacf79ca7c650688ad0baa80931adb988fe6e6e5d5903c23cc3dbd70eb/numpy-2.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b", size = 16711928, upload-time = "2026-09-06T16:25:48.461Z" }, + { url = "https://files.pythonhosted.org/packages/09/8d/41d0a56e1ac4c87495c897a211b1368691b7237aadabec8b3b8f3a74d48f/numpy-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104", size = 17059507, upload-time = "2026-09-06T16:25:51.873Z" }, + { url = "https://files.pythonhosted.org/packages/08/1e/0dfbc5cc251d54e2af790f254d24ec38637fa97ec7d5d11de7ffed787098/numpy-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694", size = 18471002, upload-time = "2026-09-06T16:25:55.233Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/dfa40f6991f8185c8c30ffd023dfcbb11888e823cfab9557b920f3bb7bed/numpy-2.5.3-cp314-cp314-win32.whl", hash = "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e", size = 6180485, upload-time = "2026-09-06T16:25:58.157Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/d2c08231e4fde7e415501fd02c715d96e98599b2d8384445933944152984/numpy-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10", size = 12698179, upload-time = "2026-09-06T16:26:00.789Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e9/dcdcc9b95cf5f49815055573aee1b11cfbf5299f38a180e437ded050810f/numpy-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6", size = 10769383, upload-time = "2026-09-06T16:26:04.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/c4/af8bc08a7ef4e1529a7c0cf24969accce316b783999802089a581ec99272/numpy-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297", size = 12132668, upload-time = "2026-09-06T16:26:07.138Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ae/0f15eb56d4ec5e13c1f7ff04ff407f997d1acbadb45d3e1f2e2645a8f43c/numpy-2.5.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841", size = 5568580, upload-time = "2026-09-06T16:26:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/23/fb/c72a8f25d4b6e96c354e7ab45ace3b27dc11e5d6a13b6c7d0cd6b08bf112/numpy-2.5.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891", size = 6882634, upload-time = "2026-09-06T16:26:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/968c90ed2ab15060c338e8137f1215b5a60756ae07328e0a60d1c6734df4/numpy-2.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6", size = 15748923, upload-time = "2026-09-06T16:26:15.092Z" }, + { url = "https://files.pythonhosted.org/packages/59/08/9df04103947b95e3b6b1f2ed1a70521f325647a31b82da6a2aae3a485508/numpy-2.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211", size = 16746748, upload-time = "2026-09-06T16:26:18.43Z" }, + { url = "https://files.pythonhosted.org/packages/41/a0/14c8d5fe5b53a334aabb653deb391c0fef49558f491880ea300ed6785224/numpy-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653", size = 17111561, upload-time = "2026-09-06T16:26:22.113Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a6/d7e96e42f01522e154c32489640f16dfc4f6181d165d05fc3bec8c2c4999/numpy-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33", size = 18513945, upload-time = "2026-09-06T16:26:25.401Z" }, + { url = "https://files.pythonhosted.org/packages/25/39/3453afb7119d0449ef11c886874120ff180e2c337760e0e2d88f70f1a945/numpy-2.5.3-cp314-cp314t-win32.whl", hash = "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c", size = 6335421, upload-time = "2026-09-06T16:26:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/99/01/22815d2b19a1a746b1d45205cffebb3fe511a18acb75fba6c88491fc9894/numpy-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435", size = 12896420, upload-time = "2026-09-06T16:26:31.265Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ee/a7cbba67eeaff038dc29ca8b98a88396c8b0cc9c89d4924f4a27a5c9150b/numpy-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a", size = 10857177, upload-time = "2026-09-06T16:26:34.167Z" }, + { url = "https://files.pythonhosted.org/packages/45/56/78194492883ff5eec90423fe56a3a44b154da047d88a6307f629713c584f/numpy-2.5.3-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058", size = 16996531, upload-time = "2026-09-06T16:26:37.287Z" }, + { url = "https://files.pythonhosted.org/packages/11/39/dd55c0af90bbab564b09ae3b0aa60ec5c02b900fa4f1ba23440525c8b32d/numpy-2.5.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be", size = 12012569, upload-time = "2026-09-06T16:26:40.707Z" }, + { url = "https://files.pythonhosted.org/packages/b6/51/04f67d32e4862b281b1cb84ceeaed3421189a84fb6fb51a391cd6d5009f7/numpy-2.5.3-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5", size = 5448498, upload-time = "2026-09-06T16:26:43.435Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c9/25b4dc0dd1344ec26c7319e84fd4e9809d2b5628f4e12decd618036e5178/numpy-2.5.3-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90", size = 6783026, upload-time = "2026-09-06T16:26:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c7/29285be1e5232a6e7ee3268a33c85843f5a8ee93350c6465cddd66ebbf76/numpy-2.5.3-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159", size = 15697322, upload-time = "2026-09-06T16:26:49.415Z" }, + { url = "https://files.pythonhosted.org/packages/55/49/bbad5335fb4996a16881f853ff3e0ba582f01720e55c89b1c06b8fc42a90/numpy-2.5.3-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab", size = 16708995, upload-time = "2026-09-06T16:26:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e9/1df35483760b04a65ea44669f89dc64f30e5aca098b48ceb8b1310b0e0fe/numpy-2.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2", size = 17052508, upload-time = "2026-09-06T16:26:56.464Z" }, + { url = "https://files.pythonhosted.org/packages/b8/99/66e54da8265cc8be8a7382bf96edce17aaa2837d6f484432025932a3caa5/numpy-2.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7", size = 18468224, upload-time = "2026-09-06T16:26:59.966Z" }, + { url = "https://files.pythonhosted.org/packages/01/bc/b5e90a91c115168d793dfd2ad9c69c438c2fe7a13a437e770bc5b078e732/numpy-2.5.3-cp315-cp315-win32.whl", hash = "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034", size = 6179919, upload-time = "2026-09-06T16:27:03.166Z" }, + { url = "https://files.pythonhosted.org/packages/37/ea/780748fd3985109075514ef8fc64cd25f943e40dde13a6d59141eb268fc8/numpy-2.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae", size = 12697656, upload-time = "2026-09-06T16:27:06.153Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/407be69a2a87c8cab64d95975a8977a426a29e138f07e276ec258f0fe4e5/numpy-2.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5", size = 10767601, upload-time = "2026-09-06T16:27:09.297Z" }, + { url = "https://files.pythonhosted.org/packages/44/bf/a97ffb01e41d50a32a9177aef942a4d0e389a3daf451d04e5f38ef6afb87/numpy-2.5.3-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a", size = 17090092, upload-time = "2026-09-06T16:27:12.907Z" }, + { url = "https://files.pythonhosted.org/packages/d1/24/136c02f2c2af9a067a84d0c3aa10c99012c0476fa5066732fa4a4202557d/numpy-2.5.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832", size = 12129429, upload-time = "2026-09-06T16:27:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6c/b47582d6597789bf946d5efbeb6b9e56fd8bcbd5efc6fbf51dbe1ea31eb3/numpy-2.5.3-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0", size = 5565452, upload-time = "2026-09-06T16:27:19.868Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/ef3cc6da73774202d4deae16bb321fd8298a4e0561e3539f8c4be237d916/numpy-2.5.3-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997", size = 6876736, upload-time = "2026-09-06T16:27:22.232Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/e3813329498596cb842703dcacac1741612ed9fb9c4e6a3e0c7e2ebbc597/numpy-2.5.3-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85", size = 15745777, upload-time = "2026-09-06T16:27:25.181Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9e/4e7a07fd0776dc2210cdacf2010be8665194d094defc10c419d7dea794cc/numpy-2.5.3-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4", size = 16746949, upload-time = "2026-09-06T16:27:28.576Z" }, + { url = "https://files.pythonhosted.org/packages/91/db/01674c0e20335057813a00c2ebd546ed25bff9ed7914f9bced00f8c55d94/numpy-2.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c", size = 17108994, upload-time = "2026-09-06T16:27:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/584c5e71f8d378e57cac0b033891ed65c683ef90573ba4854e8c28203db0/numpy-2.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0", size = 18512266, upload-time = "2026-09-06T16:27:35.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d2/4e1014173aa3c55e6a756e0e567290743a6ab33a288460374d7ef6bcd239/numpy-2.5.3-cp315-cp315t-win32.whl", hash = "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469", size = 6330292, upload-time = "2026-09-06T16:27:38.149Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b0/ff5658a58199b7bcaad87bf260eef6713d9d42cca4e028f935b4fc5fbac6/numpy-2.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551", size = 12884918, upload-time = "2026-09-06T16:27:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/b12a2df5d1b774bd9007a6fdff9381145b6223d37f11afc9c37ab0efd9a1/numpy-2.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d", size = 10850807, upload-time = "2026-09-06T16:27:43.868Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.24.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/30/7c257e3d5cb4fecb147b93895c66e29c93f8e76d74b45bb418ff0587c4ec/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:a6812a554a1ff0413e9c52b84c26c050380649ab9615f9c16bded368ce9f421f", size = 650976863, upload-time = "2026-07-02T16:23:39.248Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ba/791cffd048fe5b044e620df55267e3e95c0e6e07d50b41e377c03dfc910f/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:71f181cd810e90f9b6023b01186fe82d13d65f0ec098581ee201d39fad769e4b", size = 553099438, upload-time = "2026-07-02T16:27:42.58Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.30.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/21/a73174c6157101bdf1ffc22b517f76ff0082613989dd9bc8f43e8034caac/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:ca786ffa5a647c75d4d1f5cc72a6c4f537947e2ba8823d7c8aaf768e7a7b9f77", size = 215983881, upload-time = "2026-06-09T03:23:15.633Z" }, + { url = "https://files.pythonhosted.org/packages/3f/34/c500f90c7ae641b8e0f98965b36b8a7ac79cc8b296e8d251fe3eb592ee54/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:cefa7fdb9710efd0f39c5f1be1d61ff6fc9a996c451265bd7fbdcf9455ed4b50", size = 215965170, upload-time = "2026-06-09T03:23:39.73Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.4.52" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/c1/091f198d7f87e31d67fa9680eec8f8e4c6f889881f729b759db36ff01612/nvidia_nvjitlink-13.4.52-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:90401db7e5a580a5067a468b3086e0b65f3b96ab3c42524afd741efc8a0e150a", size = 42452221, upload-time = "2026-09-09T18:01:32.895Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/225ff51e80de170be880cb88e992193bc8134a51059cc0a3952f967f62c5/nvidia_nvjitlink-13.4.52-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a589e3732140839349545efd0db67426523459b12e6952c3c73b6d55900f200", size = 40419746, upload-time = "2026-09-09T18:01:25.103Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pymongo" +version = "4.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/56/8c52e7f57719051748378fe534fbf916440b1eb8fdb8053c9b43003c9865/pymongo-4.18.1.tar.gz", hash = "sha256:d04134ff4ea0e7d14b6e2ef23dbfe06112a99318c9578b213d2dee9060b23dce", size = 2744931, upload-time = "2026-09-10T14:57:11.817Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/ef/869a383a4ac3e615599a35340c9bcf02ecbc9a5059d2227b8f96ba6af38e/pymongo-4.18.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:67d56552a940d3553024fdf39535975c4de2ff5b82cf20287ba1ffef028f004b", size = 818559, upload-time = "2026-09-10T14:56:15.241Z" }, + { url = "https://files.pythonhosted.org/packages/d0/30/a1b0870b71efbd49ee0affa61bf8be067acfdc3f91244443a3395edf9c94/pymongo-4.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae18db68105a0ba7adc190a13cbd73e6c87593bc55a336719f70ad042aa66aaf", size = 819124, upload-time = "2026-09-10T14:56:16.9Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/40e08f302e66a3d4da98c7679136f674f736e24c0ac4b306567a0b5cbf0d/pymongo-4.18.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4541273796aa3022b39de892e265aac4a437adcc6e4894396c23258d990f8c97", size = 1040989, upload-time = "2026-09-10T14:56:18.508Z" }, + { url = "https://files.pythonhosted.org/packages/59/d0/7bb489c716c64c5e3c77df69d5e38ea67374ec2e75e5af413fed263dcc1f/pymongo-4.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3f52a1a793262ef1af15b36535220d194b3ad5ce7564f98c15f3881ffbc269", size = 1050963, upload-time = "2026-09-10T14:56:20.259Z" }, + { url = "https://files.pythonhosted.org/packages/53/31/09c78af31758923c0b39aa4ec934cb261446619453c7e263ed8d38d4abf6/pymongo-4.18.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab33db087dc52f8f28bdb8680682ebc8fc5fb0be84e89a0853a8dc059fcc06a8", size = 1074988, upload-time = "2026-09-10T14:56:22.057Z" }, + { url = "https://files.pythonhosted.org/packages/1f/dc/83956070a4313cc9efa676f45d742240540078a44727fc52e3521a15e8a5/pymongo-4.18.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:173a69caa539731284a815ee2fd616442431d12e8c84d970f3cc555d9ac5ef81", size = 1064428, upload-time = "2026-09-10T14:56:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/d6/05/8817d987e5eadd127a4f44d19ce7a31e507c300d6f1511de99bc73f880f5/pymongo-4.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b46ef2de0b077a2c708790e9ced5c8afb5b9deef1cacba28e8d9a90027e86d2", size = 1049218, upload-time = "2026-09-10T14:56:25.288Z" }, + { url = "https://files.pythonhosted.org/packages/c2/24/b3c38caeb743a7055a1cc29afe2edee06206fe2406e90b3e0d4900242df0/pymongo-4.18.1-cp314-cp314-win32.whl", hash = "sha256:d9be9221bf2db1562d490bb67522ce2f7378513bc8bcc157e824a0bb3d87eba0", size = 816052, upload-time = "2026-09-10T14:56:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/02/0e/e1a8406b5361b06043762b7f50a13a3d9c04b2ea8202f39c8cde737a8f13/pymongo-4.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:134a8f787360c3cea18cbbc126bf51cd233f1789db8099f7081ace5b1cff7e22", size = 821969, upload-time = "2026-09-10T14:56:28.571Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ff/82420a828245bb3bf36dcb6a59eebd914fba78dac87985c48145d03a405b/pymongo-4.18.1-cp314-cp314-win_arm64.whl", hash = "sha256:088e78ab7340d7ab4c14cd7a96852f9ea3b51c25fc9fb02244715d21f57ec2e5", size = 816505, upload-time = "2026-09-10T14:56:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b1/fa1c5cd5ea032ffbce4f4c7876403405405c9cc9de7abba33c78254bd278/pymongo-4.18.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9bf92d46b15fb8fcfcd517442b0b90055924d835fef77e574ff4678b40be49ce", size = 821533, upload-time = "2026-09-10T14:56:31.96Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cf/41c06f9cce2035bf061248d1576439bc60701852f75118fd2b5a3408abd0/pymongo-4.18.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:58c3392585c5823ad90e318b6e7c0fb0709763631fb06d81de45166a1844ea18", size = 821971, upload-time = "2026-09-10T14:56:33.83Z" }, + { url = "https://files.pythonhosted.org/packages/53/3f/c698242cc5013a0d99099db622813c480e23dac7c8bad3852610fb5a5145/pymongo-4.18.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4b8f7b212ab86552db2f35b2779cc290561a341b73fba1ab1030ed9d48e1b1e5", size = 1105379, upload-time = "2026-09-10T14:56:35.497Z" }, + { url = "https://files.pythonhosted.org/packages/37/a8/02fc8305155d46651c0e3980998d3da881db287866be8a5c98d9a0fb67a2/pymongo-4.18.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2ef166249993afd94b704bd25af27104fccb259580960a41355f8d7869496ed", size = 1125164, upload-time = "2026-09-10T14:56:37.216Z" }, + { url = "https://files.pythonhosted.org/packages/53/0b/7fcdda4b6e07497ce63d59a932ce193e3da098c309cb18a2d55ed7c99f84/pymongo-4.18.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adebc03de2d1520bb163953361779288316e4e9c4b66fe4ce9e91d4183e313b8", size = 1144618, upload-time = "2026-09-10T14:56:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ce/b9213ece00a38f742ca4cd6afb812d73d4cfe857b1018e90f803d72c4b9f/pymongo-4.18.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9ae9c28e6c290c2524c3288a0a6e27dca958daeeaba2f36a6a4ee97c44e5f49", size = 1136374, upload-time = "2026-09-10T14:56:40.647Z" }, + { url = "https://files.pythonhosted.org/packages/c4/42/aa057e91930209d731ec3fed03edf32cf6aa59838f7766227ee219142574/pymongo-4.18.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5907ccdcc2f7925100b4bed8069acd0d93156e5cf013a8e15aa5932819ce3d5e", size = 1117151, upload-time = "2026-09-10T14:56:42.561Z" }, + { url = "https://files.pythonhosted.org/packages/00/a5/86777256266850eb2217d6c8c8f382a213a1773c144383d51f61bbb2e651/pymongo-4.18.1-cp314-cp314t-win32.whl", hash = "sha256:9da74b79e382bb37311b9bd5beb89f1369a1c3e27e7c97bae79661cfdf3893aa", size = 818806, upload-time = "2026-09-10T14:56:47.024Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/08e9256de9fab497465e7e6589fab4d40ca266dcceb88d3acdd0947095e1/pymongo-4.18.1-cp314-cp314t-win_amd64.whl", hash = "sha256:016d21e7fbf55ac89f419daa48da95314896205d902ed9aa4058f26a436e789c", size = 826141, upload-time = "2026-09-10T14:56:48.886Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/12afe9d59fb82b60832356f9fe8f454a99371c0e0a42f0ec7856f5c893fd/pymongo-4.18.1-cp314-cp314t-win_arm64.whl", hash = "sha256:960c61ed86a316d3636b1f184006d3cc2b972b8546186ae8e2c8324e397632ff", size = 817669, upload-time = "2026-09-10T14:56:50.602Z" }, +] + +[[package]] +name = "pypdf" +version = "6.18.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/c2/e537e78a8282b51ed47031566be118debbc34c04782a9afd8739672eb07d/pypdf-6.18.1.tar.gz", hash = "sha256:2441fc839053745638d9b9ddb38ed66beed8d611f47a3036c9838fc4e3074dea", size = 7027577, upload-time = "2026-09-11T10:10:07.616Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/13/645df3995075112cb3cce15e8797c205f0f88fb50acc11012b84b071bc22/pypdf-6.18.1-py3-none-any.whl", hash = "sha256:ee93a2665670ecf57ee81d197a4ca548f3dc15f9cefc56e59b8140866aaa3de5", size = 394327, upload-time = "2026-09-11T10:10:06.103Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "rag-ingestion" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pymongo" }, + { name = "pypdf" }, + { name = "python-dotenv" }, + { name = "sentence-transformers" }, +] + +[package.metadata] +requires-dist = [ + { name = "pymongo", specifier = ">=4.18.1" }, + { name = "pypdf", specifier = ">=6.18.1" }, + { name = "python-dotenv", specifier = ">=1.2.3" }, + { name = "sentence-transformers", specifier = ">=6.0.1" }, +] + +[[package]] +name = "regex" +version = "2026.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/5c/f403115361de25809e8f785686ec7096e30fef73be9ae35aa51da4e80abb/regex-2026.9.10.tar.gz", hash = "sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d", size = 417072, upload-time = "2026-09-09T21:00:21.521Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/ed/98e9b07d8bb9c765d07774f0b2c19b301b96d51f44630fea48951051c94e/regex-2026.9.10-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd6bd89b9fc06018d35851cab0240adb7dd84d51941b19f6574ac90cd54e3ae5", size = 496662, upload-time = "2026-09-09T20:58:05.118Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a8/9dfe9be48378b47c5a8f04b0f200ea225f9ee0f8e93f010433f661a37878/regex-2026.9.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ef4c0a9dfdc90581b90b1b95a8c3d1557f8ff8f5a2a53536d26314de699d1468", size = 297115, upload-time = "2026-09-09T20:58:06.822Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e4/5d1f005a3825ec49842ad349061c1c26e6d42f47ccf105e6e5aa6aeed392/regex-2026.9.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14caa05ce39ec70437af5aac8814c50ee6628f4a90353871c059692f448a164f", size = 291896, upload-time = "2026-09-09T20:58:08.675Z" }, + { url = "https://files.pythonhosted.org/packages/be/15/44ce83fca50c6058f42b62fa8300a8030eea7e4e5a973a2dd33db0f557fb/regex-2026.9.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3264132d576847ab5f88bb83e7debe67854bf165b3ea613bd467312b6099536a", size = 800534, upload-time = "2026-09-09T20:58:10.339Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/a62e070a5a033643b287489576f02ae6a9c584c337d62349e340a2b4d001/regex-2026.9.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5cef9f3d14796500ea834c41dbe688f1f6b23c7024dc23e8a794d7ebaf5d71d0", size = 872038, upload-time = "2026-09-09T20:58:12.186Z" }, + { url = "https://files.pythonhosted.org/packages/f3/06/8b8e2483949b1329df10c5b615e85d332066deb426b11332b799629b9201/regex-2026.9.10-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d278ad30ec83b6b9202685b0f80b741a51ea3ca7f0595ebda96e7628b6398876", size = 918927, upload-time = "2026-09-09T20:58:13.903Z" }, + { url = "https://files.pythonhosted.org/packages/bb/64/9b56f69100d3afdbc9c4fa6e302764f9cb717fbc06a9d50558d98ca89cd2/regex-2026.9.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb018cfe7144585fc83882405906ff84994a2d154afc2509ecc7752c51f864", size = 803694, upload-time = "2026-09-09T20:58:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/07/43/d00d59a7c8fd0e070ae8457a8743597f45ad9682b100f57b9c9c405fbdfd/regex-2026.9.10-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6fd555fc9abef50c530869690b2daca054c8811a7aff632d11f9a7b2590b2742", size = 777770, upload-time = "2026-09-09T20:58:17.628Z" }, + { url = "https://files.pythonhosted.org/packages/46/6b/a11d0446484efbc9eb67abec133f254c6d66a1568b8f3fb36d39a73a1129/regex-2026.9.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8d5c4518235a2ec1611e57af85fa488d529c1106aacff12adadcedf8687012cd", size = 791234, upload-time = "2026-09-09T20:58:19.476Z" }, + { url = "https://files.pythonhosted.org/packages/12/09/bcd24e78b373fd4f98090caa43eade439223f6703b909be79b9efd9ab0ab/regex-2026.9.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:175cf49ce7a994c88b8f15e3cb17cdb66a48ebb2d36de736b8205033db950f89", size = 866259, upload-time = "2026-09-09T20:58:21.683Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/c57ba5e94222a813260af4c17ced92d40cdb44737eb6b50979688310b6a3/regex-2026.9.10-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b71649169a9fcf30b395ee01047fa7ad6654a4c900ca75b23c04dedcce6a1f8c", size = 768219, upload-time = "2026-09-09T20:58:23.842Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a2/3820037587d00901ace96c5864335c2cd1b899d5263ea0dd2261359e0bee/regex-2026.9.10-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8ba1f78bd4fef2d8f84b894ec28ac3481afe6cc07aaa253ad4717ef7b3fe6bcb", size = 858582, upload-time = "2026-09-09T20:58:25.607Z" }, + { url = "https://files.pythonhosted.org/packages/47/f0/f9a838ca6219ae4821de0175e4548db73ef56de5ec08d032fb427732fa07/regex-2026.9.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:217e98ba5fc8908ed8ffd4ebac04753a0c831067cbfb495b9821b94cc61eaa76", size = 791405, upload-time = "2026-09-09T20:58:27.406Z" }, + { url = "https://files.pythonhosted.org/packages/f7/bf/67d71cc4e13ae2e0022d21243ca069e0868701a99eb29cdecd13d356e694/regex-2026.9.10-cp314-cp314-win32.whl", hash = "sha256:b298cdc33c5cc6969ff07f0fba19cc73e0fd8576373c50935feadaca2f6b4405", size = 272702, upload-time = "2026-09-09T20:58:29.116Z" }, + { url = "https://files.pythonhosted.org/packages/c1/38/40a93e72703a741235115ed1b1e5f6b869917677b7643005034ce1611d70/regex-2026.9.10-cp314-cp314-win_amd64.whl", hash = "sha256:c32818b28bcd153b25b63038348a9fe9b9fbcddb60df43f204c3ab55eeb57f77", size = 281170, upload-time = "2026-09-09T20:58:30.899Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ac/e95387f00617c16bb41b786d900bacd17eab88afa81b4f263df532b3a731/regex-2026.9.10-cp314-cp314-win_arm64.whl", hash = "sha256:75242f44a3e283106077be4ab717bc535e4701c9d54ad69e195945c22f137a1d", size = 281511, upload-time = "2026-09-09T20:58:32.594Z" }, + { url = "https://files.pythonhosted.org/packages/39/e5/a4b12262edc488a8a7a95b672db317dd8aa9bf2fab98297f9c91bb11ad4d/regex-2026.9.10-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2dd9286093c71afc8f55ef035c5b9d2776641fd72c6535f1febc92d0b0be9666", size = 501128, upload-time = "2026-09-09T20:58:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/55/c8/9ca31c0fa5197ded8614c8ae0e105bcff2d979025ffa3c75580baa334e2f/regex-2026.9.10-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:71879292c9c7ac67b1680345b16daba1be937cb027362cfa04e68f65db2dcfdd", size = 299428, upload-time = "2026-09-09T20:58:36.296Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d1/ee7662561735f90475443c3ca1977e5cecaeaa8f29620dec75580aebc839/regex-2026.9.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ccd139b2061132e7b265cfb4b4721baeb9f8928b81415304abf1ec7e3181c26", size = 294494, upload-time = "2026-09-09T20:58:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b1/333168e45ed6cfe71f6d17e21e5f54725f44d171f84edd12469a2f739227/regex-2026.9.10-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7327795089ddb44912dce1434e1d7244be2e9fb48fcc2d6782936af7a3062db", size = 814925, upload-time = "2026-09-09T20:58:40.438Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a5/df1b38536d0a3b24a030eb4130ce98b403cc925220ff530f5313a7c436eb/regex-2026.9.10-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff4d7b14ea19e50c8d9d6d83f45bd9b45cbb624c07ac1fa54db0a019049abed7", size = 873323, upload-time = "2026-09-09T20:58:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/93/ea/aa71fe62dd63a8336bbdec1ff002a6c53b40ccfca95961c18ee4f09bf03c/regex-2026.9.10-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f0407474ffac8e5e89d93ca41d60891e29f0ab8423eb66ff292d850a86a0843", size = 923080, upload-time = "2026-09-09T20:58:44.488Z" }, + { url = "https://files.pythonhosted.org/packages/86/38/49f8d6fd34fc1a9c75b5b96364ebdde702a8144e5ed63d2213c58d454c27/regex-2026.9.10-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ad10a135fa0b4e4a462a61d07c6654d7518cfdb5cb8da08f9ff7d61384af1fe", size = 821284, upload-time = "2026-09-09T20:58:46.56Z" }, + { url = "https://files.pythonhosted.org/packages/32/45/91a977c96d4be13d1ade8208c260c26841c836d4c91745e1828a30589070/regex-2026.9.10-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9fbd2e5d8002dc49a6129fb321ec51c57a025e752ed525ddce0ba9223c4350a7", size = 789256, upload-time = "2026-09-09T20:58:48.473Z" }, + { url = "https://files.pythonhosted.org/packages/2d/79/4d110bf01bf9651bf9b3f88a6d9fa7e643e0586921a18432b25a478edbfa/regex-2026.9.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4a761ea45f2ad74c575ef5850ea514cef97302a552d3c7c9d1a1a870d4661d6c", size = 803722, upload-time = "2026-09-09T20:58:50.286Z" }, + { url = "https://files.pythonhosted.org/packages/e7/67/b587a0d3bbac2635309ed9c40c197120c39e1ec0afb8813cbed1338fdd75/regex-2026.9.10-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:75aa39d3f4f1650eea84e46b0d8cefe77dd5478c10e3d0aaf0b0f00493475a7a", size = 870085, upload-time = "2026-09-09T20:58:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/07/fc/0827bca20ddba1d70fa5111a2a64e6b6b38bfdab43fc435022b54172a111/regex-2026.9.10-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f5c629df03adec31ee505dda3c8988f106c9390e4cbd343600036eb8b3d6724f", size = 776970, upload-time = "2026-09-09T20:58:54.478Z" }, + { url = "https://files.pythonhosted.org/packages/9b/03/ab9d08d30568ca868791bfb99551db60b947f05e2e65dcd1261e87083c21/regex-2026.9.10-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3a66e40a1a20de96a2fee00ed67e11012b62d85b277688258677fd19997addb7", size = 863611, upload-time = "2026-09-09T20:58:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6d/8063ae86b543ae878a7d6e7ba21ebf0af4af06161230e0012e2d652320c6/regex-2026.9.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:968c1e33edd9a104d1bf24c8d476c72de7e3839ae7f894b37e9e4f4739fdeeca", size = 804064, upload-time = "2026-09-09T20:58:59.066Z" }, + { url = "https://files.pythonhosted.org/packages/c5/63/b19305c4b8d3d7867699f7d83c43550273d91a2f31793452d87edb1d259f/regex-2026.9.10-cp314-cp314t-win32.whl", hash = "sha256:fbc4e2f3cb7ce8436154e6483079e7d35eeb321a952fa936e180300630d8b873", size = 274607, upload-time = "2026-09-09T20:59:00.942Z" }, + { url = "https://files.pythonhosted.org/packages/32/b8/1695072a512a49060294024e23b945eeb87675b02c06e53a92a1b42b0bfd/regex-2026.9.10-cp314-cp314t-win_amd64.whl", hash = "sha256:c37fa93bf18bf4f90b01c0fa9f11ea567ee4b7dd8bf96e63663e5edc37aa38cf", size = 283944, upload-time = "2026-09-09T20:59:02.876Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9f/65bac17f39991a67e22f8b3c849fdc02a56147c97029b5351494341959b4/regex-2026.9.10-cp314-cp314t-win_arm64.whl", hash = "sha256:ffc2da104e43db716ce30cef9f28049a1faa6aca385dd8771b033268d0730b07", size = 283780, upload-time = "2026-09-09T20:59:05.009Z" }, + { url = "https://files.pythonhosted.org/packages/67/ca/1d1f83bc2f8fff4f186266ac82d73254e686565530cec9ab5228fb5c63dc/regex-2026.9.10-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:6afcad14310f1311d077553ed374b42a5e538f85a8c884b4e38e52de091c8077", size = 496869, upload-time = "2026-09-09T20:59:07.051Z" }, + { url = "https://files.pythonhosted.org/packages/33/42/4217510286501a2ebcd372b781b4754ac961e043fa13ef8dce803c44d89c/regex-2026.9.10-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:3fb4ae8cf83ef4e9addd43b2da31a9f45be816a8036fae8af59c8998b72718e2", size = 297121, upload-time = "2026-09-09T20:59:09.007Z" }, + { url = "https://files.pythonhosted.org/packages/55/a7/595468ed0bbccd94be92c6b5d67736ba128b204d942429a8485c2693914d/regex-2026.9.10-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:f7d4656e17ab736e9415a6442a345bfc97bb8b7dcce47884bb74a37f70f08d0c", size = 292139, upload-time = "2026-09-09T20:59:10.859Z" }, + { url = "https://files.pythonhosted.org/packages/29/1c/ac92c123e0ab9bea75a904272171b356940bd6139e4a35de44f2254dca8f/regex-2026.9.10-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ba3bab0c45079735f55ac61526774de1d84bc4a0333cc554e1a4ab74913924", size = 802375, upload-time = "2026-09-09T20:59:12.823Z" }, + { url = "https://files.pythonhosted.org/packages/8d/16/d349f6fa9f908162359004e4f067353a0146e8074d24989490778244be44/regex-2026.9.10-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff6b3267318661dfddf6b3628663e00e5946bd0a5c8fa678537a1401f0388f91", size = 872328, upload-time = "2026-09-09T20:59:15.352Z" }, + { url = "https://files.pythonhosted.org/packages/df/81/251b5aef23147057e926346fdb7c8c352d0568f65a492e4e9eb6120f6446/regex-2026.9.10-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:030fa9e23624e39b3b94e46b90a5abd1a1678eb2f58fcdd3fd6c27526bf91c7e", size = 919594, upload-time = "2026-09-09T20:59:17.31Z" }, + { url = "https://files.pythonhosted.org/packages/d0/6e/1f25319dc1b9cf4b7ffa303f3d16ca53260fe92aff45e81aa1b3c6c7cba2/regex-2026.9.10-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fbc8314436353e097c050e11b01a6c11433579437ed0579730157676ef59e2f", size = 807394, upload-time = "2026-09-09T20:59:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/cc/fc/3da6b3dffddf12d5e96cbbe6f5e65ebcd64f92e3388a6690a53e0878232d/regex-2026.9.10-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e6c0b5ec6ddee4032247585dc491b0fa58627745b66a705728703a3f0331231", size = 786018, upload-time = "2026-09-09T20:59:21.592Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6f/1cc86dafddc912ef44c5ccd4f729060be8c6735600932664eb6d0e25469b/regex-2026.9.10-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:bf29611e5376fec8f795879bb5c6153a76c3a292573d173c26784042b01eb840", size = 793504, upload-time = "2026-09-09T20:59:23.734Z" }, + { url = "https://files.pythonhosted.org/packages/b1/cb/11692e29388d006211163627fcefa0c79917ab9f94d46a1b2254fe5efc81/regex-2026.9.10-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:ec8855f08c17895a26fbf5f19ed829722e19b34a96629e49a43c92974924026b", size = 866766, upload-time = "2026-09-09T20:59:25.798Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/3631df969830f94d83fcfc5fc71a7b39caad905e18f7b17350a94135d625/regex-2026.9.10-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:94c5ce3bc41d226b4eb89ca3f842b2e28c031487fb1f34eb2153d98235831325", size = 775825, upload-time = "2026-09-09T20:59:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/762892a8e3e21d119eacaffde848aa247e6cf4e1d90c46011671e86b1b9e/regex-2026.9.10-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:9ce239acb15843ab03976626af810a4424b0409689ec2bbc52088ab5479ab487", size = 858901, upload-time = "2026-09-09T20:59:30.656Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f0/78048fada5c2f61d795efb26ea24f820a5564c4aa26574920284d45cd656/regex-2026.9.10-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:c22df8dd6373bbe3898e77429ffc85594300e39d752fd0e68a31e59d37899376", size = 796208, upload-time = "2026-09-09T20:59:32.852Z" }, + { url = "https://files.pythonhosted.org/packages/aa/58/632681f7b9aaa3d83b40e5862ba46364a453c8eb2bc7d43fece3dc31f972/regex-2026.9.10-cp315-cp315-win32.whl", hash = "sha256:1aa309ab7ba89a62d6cf70dbd38d4176440bce3c7001ab86256704cf4c18c6eb", size = 272704, upload-time = "2026-09-09T20:59:34.927Z" }, + { url = "https://files.pythonhosted.org/packages/5d/64/81cce28754c37037b1fe740b6d7a556d51d97cf935ea4547bfab104f43f7/regex-2026.9.10-cp315-cp315-win_amd64.whl", hash = "sha256:58da726d3e766c0b3f5a3997dfaf0275898a1107b8191cdd6b0437fe45fd817d", size = 281182, upload-time = "2026-09-09T20:59:36.86Z" }, + { url = "https://files.pythonhosted.org/packages/33/28/5a13a340c9c759e863a0e7f765d323601d03d6538c8a627ed628662e083b/regex-2026.9.10-cp315-cp315-win_arm64.whl", hash = "sha256:75f9297b16fcb588a1f8d8a55dabef3c0c20b0c7bac43c87ceaaaf1a825c12f4", size = 281512, upload-time = "2026-09-09T20:59:38.875Z" }, + { url = "https://files.pythonhosted.org/packages/f7/38/a3caebcd5105be90708071db20bd261b0961b8ea4fe5e8be45c2632519b5/regex-2026.9.10-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1270cdec69248592bbe38a0b263ed58d907b891bd2b93703e225c317e421bda1", size = 501336, upload-time = "2026-09-09T20:59:40.987Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b5/ec8887b2658bf0a5df143c7c1fcd562b0abd2fa208c04ebf15c6607c9bb2/regex-2026.9.10-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:681ed38664b64c6617d3c3c332018d1948c77e139c5ea667c1886efa671e426f", size = 299318, upload-time = "2026-09-09T20:59:43.039Z" }, + { url = "https://files.pythonhosted.org/packages/d5/58/84724a9eccf6e8cd46f7e4534576093e2476a5eeb55e2453aa6606e86059/regex-2026.9.10-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:8e127d9a80cbf1c3276bb465c6d047e8705e97b58c2b8f2f0c0a69c336b44b37", size = 294847, upload-time = "2026-09-09T20:59:44.954Z" }, + { url = "https://files.pythonhosted.org/packages/50/03/70ccc5e53905984abf8eab63eebd3ce740522ef8c8d392622b64d79ef290/regex-2026.9.10-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:990797e765d89a423880052c68b61c31afe701de94a8c060f61c40605ca6c727", size = 814359, upload-time = "2026-09-09T20:59:47.194Z" }, + { url = "https://files.pythonhosted.org/packages/29/53/40f7a11ec547e4a947883c9d5e8a075f6d4f59af2b8dbcaa8bf5b504aca0/regex-2026.9.10-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e5e4a6e0734a685d13b9685622bb503bdbb2927f8b0df025a5085f0ea067475b", size = 875586, upload-time = "2026-09-09T20:59:49.537Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9d/83f3e022d99ce601727c4ef5f7b527753901ce4509ed89a1bb6a2263380a/regex-2026.9.10-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:63bb62cf62217dc38c8a6b2b61b165b0e4eb8fa93b0aba12139251c0986a8fa3", size = 920990, upload-time = "2026-09-09T20:59:51.776Z" }, + { url = "https://files.pythonhosted.org/packages/b2/64/dfdb367d8f4f5c9b8ccb4b59789c2ce996f2c69c3b8192aa986fe7d92ec4/regex-2026.9.10-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb76a9c4e07a6a47849726af0ed14c41741a182f097f134a8cf29c1bc0f4dde8", size = 818660, upload-time = "2026-09-09T20:59:54.428Z" }, + { url = "https://files.pythonhosted.org/packages/88/a6/fb7d0b64487913845834f319aa84f8377d55305958a5db7e19c128798366/regex-2026.9.10-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31e4df2b11d48f61d511019bc1ee9b477055f17c352b68fe72db7a98b14d603c", size = 794976, upload-time = "2026-09-09T20:59:56.799Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1c/23484edaae387ea0d31f4d414463211e3516c8aa210ce8d0640f5aff3502/regex-2026.9.10-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:cf377960d2ac37d987394a9dbaa75e91338c41a46d41e1d25e90125e7b3ee2dc", size = 804081, upload-time = "2026-09-09T20:59:59.39Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/e30d138f13aecec99ea9aecef7e31563de6ec6a2f5ce1d65fc506aee33fe/regex-2026.9.10-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:c8fbd9cb30c68c1686b94029b9ef845d5870d3d65baf66cb126b676849b9d72b", size = 870430, upload-time = "2026-09-09T21:00:03.304Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/81f9ce86f7ae4f57901543597c95751fa01c41a672ec1636dd913f8a000a/regex-2026.9.10-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:53e182b6b04d0011909b47d51a2d72d908de07c7b1c7f16b3adda2204d723bc1", size = 783327, upload-time = "2026-09-09T21:00:05.68Z" }, + { url = "https://files.pythonhosted.org/packages/a9/1b/d7bf8f91534740f6a8ca17e5ac9c5337903baf527c8de240fbac1bbedfd0/regex-2026.9.10-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:0aa7589394230e0f0a422ab6b90841ff12c87e855e7aaf75d192a54a5f124548", size = 860874, upload-time = "2026-09-09T21:00:08.41Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1d/52cc88364aca7c9013dfe9abe0fea67f8d394efe384d07798300a6e2f27d/regex-2026.9.10-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:1b891f77554bff991804cee24b78b40789f7d5993a24c7907bc7025fd2a70c8d", size = 806705, upload-time = "2026-09-09T21:00:10.768Z" }, + { url = "https://files.pythonhosted.org/packages/cb/51/37a194df7707f92f33173260ba7b221d4ec376419a53babaec50019a2804/regex-2026.9.10-cp315-cp315t-win32.whl", hash = "sha256:5bef622850cf760154719d4e0d74b0a855962432995168e250069899ae12fe8f", size = 274780, upload-time = "2026-09-09T21:00:14.003Z" }, + { url = "https://files.pythonhosted.org/packages/77/12/3227a52970d90908b230f15b2c86df903f49ac72c9eedf4f6e8b5bb5e1a7/regex-2026.9.10-cp315-cp315t-win_amd64.whl", hash = "sha256:07b45ba5c94b8fcb30cb6c56a11f715c57533a3017964504322ea52690a27b72", size = 283936, upload-time = "2026-09-09T21:00:16.275Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bc/c567c5a61671f04d30e83f20b496b465432879196574d051007285576205/regex-2026.9.10-cp315-cp315t-win_arm64.whl", hash = "sha256:f70b9f0e39c2dba1d9da6bf7ef7c377cad7277f8440e9a69be05ede529ff024c", size = 283747, upload-time = "2026-09-09T21:00:19.113Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "narwhals" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/eb/eaf5e07fcc0da7149b0e084f24e54edd7441b9a89ce7e034032ae97fe3a0/scikit_learn-1.9.1.tar.gz", hash = "sha256:629cada3e33e2b9bf376cdc7614a47a4140b8aedc1d836579e359736fbd82977", size = 7786908, upload-time = "2026-09-10T18:34:04.679Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/e3/b58e45082dcf3dcf0eb1192ee03545ec43d8c98441dfe20e88afca8442ce/scikit_learn-1.9.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5d117952769b563067656784e03c75a2d8235a7a05cf7fffa78a311e75aac08", size = 8747414, upload-time = "2026-09-10T18:33:10.698Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ed/d68115577c8b42b0442ebd8180945d4008880a33176094640e00b585128e/scikit_learn-1.9.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:8893bc6331f60f18d4ac75e12ed356e2dcf6a564bf767918b5b7ca54c8c8be49", size = 8279175, upload-time = "2026-09-10T18:33:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6c/06c7eb61a438e389cbf3f7210897069bec5a883dbe03c189ae792781e11a/scikit_learn-1.9.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5492cf2df5226691c32611de8734bcf42148c6547ae53c7f4e6b847793addc0", size = 8881706, upload-time = "2026-09-10T18:33:14.741Z" }, + { url = "https://files.pythonhosted.org/packages/86/4e/0bab75490ca4b85fad8388739c7ebc71d9db553f8c69e39943ee8db0aaae/scikit_learn-1.9.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993d332ff80e62efae9e39603b7e872297c418d780f01a01855269a3489c950f", size = 9152030, upload-time = "2026-09-10T18:33:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/9a/13/31c6f8ba1b7eecef9dd9558576c752d2ec5785fd0e456bb9fc59305be23f/scikit_learn-1.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:ca9051447455dae341d4d591eece7deb2d8e3d1020298fc87a81fc51e4da8f53", size = 8377024, upload-time = "2026-09-10T18:33:19.941Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/6d3cb8a45f5228f915acd66b819dd6b8232ccbe24532f51d278e3991df31/scikit_learn-1.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:90de6573f733a9fb79476ff1371af52a397d41c8b35f9146e20923db010d67b6", size = 8014196, upload-time = "2026-09-10T18:33:22.126Z" }, + { url = "https://files.pythonhosted.org/packages/66/6e/6befb2d5961490d18d9dbc16a5df37aa121d08bc9893316a6a363977a903/scikit_learn-1.9.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7b5cad1624de8b75e5b9ccb7b0ce1ff1d01306340a3efc56d5529c5ba92392eb", size = 9066066, upload-time = "2026-09-10T18:33:24.396Z" }, + { url = "https://files.pythonhosted.org/packages/cb/18/11271f2f7db337db01f598e358721b1e83989407131272e5dd64214c28a8/scikit_learn-1.9.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:d137ce8a6142029fb5c35bd82f470c40cd9e760e5e2f7694b362c497c4ab3fa2", size = 8647654, upload-time = "2026-09-10T18:33:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/e7/04/9c15d201e1b6a2e81b8215865df7646c5a360f560831769c8dc92ac1ab9a/scikit_learn-1.9.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66f852f7325b5070bc28329005aca76055a2def78faac039548ae889aeaa45a6", size = 8889070, upload-time = "2026-09-10T18:33:28.95Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5a/4cb6c85160af4a639e87a3b7bf8b1c25cfc3b504c5af710ca416a6dcfc5f/scikit_learn-1.9.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:748bcb0a4cc04aec470652c9e5ec68450948e867387e7dfade647107ade68d25", size = 9131227, upload-time = "2026-09-10T18:33:31.008Z" }, + { url = "https://files.pythonhosted.org/packages/00/0e/361440972ae3d19b90ea88a84791138a51de0e8432a770ba741b2c8d9ced/scikit_learn-1.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:38cd925e893e5539be704d5edc64dbe081aacdab6b89d8c2977c1f6a7a453ce5", size = 8683001, upload-time = "2026-09-10T18:33:33.188Z" }, + { url = "https://files.pythonhosted.org/packages/e2/8f/a9f405c5c0e2df6f343a871b40c97fb32969e3ccc38e3033dd118f3c261e/scikit_learn-1.9.1-cp314-cp314t-win_arm64.whl", hash = "sha256:b01e5b01735d38474127ca3f49319b592506225a87793b27559816b5c75cea39", size = 8257996, upload-time = "2026-09-10T18:33:35.343Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c5/74a83ea39cef7cd07f53e06cc1cf51e79f35df74f81db956835d59ec34b1/scikit_learn-1.9.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8dec64f31a6e0ec826aca6c1b39a51e16d946e400d4c0904316f3ca72ccfb825", size = 8747991, upload-time = "2026-09-10T18:33:37.4Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/cc93e8a7fe204e43d70e96e5eb89871643be20025eea05eb4fdaf19afe39/scikit_learn-1.9.1-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:e1b468241f4a7a9a7a0d6479ad3cc47681cc151a4046c530f2777c3d68f08942", size = 8279340, upload-time = "2026-09-10T18:33:39.705Z" }, + { url = "https://files.pythonhosted.org/packages/a2/61/0c6080f0d356fb966053009e7f25e9bff6cf74b0b16195ccf0c3757d1ae8/scikit_learn-1.9.1-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ca869d0080a5723cde2d5a8b54a2da1ff7e68735a9e9adb3da1243183a0fa01", size = 8881917, upload-time = "2026-09-10T18:33:41.912Z" }, + { url = "https://files.pythonhosted.org/packages/47/bb/98a31f10fffbd39edcc2f8bf4119b29652248bb110b7d45c84e68aa293ab/scikit_learn-1.9.1-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6754b7cabfc3df0b1f7b38f7a344f559bbae9d82f0ac5e3d48cccbd19fdcefdf", size = 9152805, upload-time = "2026-09-10T18:33:44.23Z" }, + { url = "https://files.pythonhosted.org/packages/f6/48/858ceff52213cfd97c0a069362071756bcb70b9fac9771b38d87c4cf7f17/scikit_learn-1.9.1-cp315-cp315-win_amd64.whl", hash = "sha256:52cfdb1fed3a34362dbc0bd96f2e761a66fd5724d6901629f5a558f1f3bd9849", size = 8376719, upload-time = "2026-09-10T18:33:46.492Z" }, + { url = "https://files.pythonhosted.org/packages/2a/1e/5337a871bdea53effbd154b61429df048f2665653251de74a3bd8a6dea9e/scikit_learn-1.9.1-cp315-cp315-win_arm64.whl", hash = "sha256:ae6571a4828c6f5019bcd2b4125e5b18c0af3dbc9c99726c891f45f41335ec8e", size = 8014341, upload-time = "2026-09-10T18:33:48.762Z" }, + { url = "https://files.pythonhosted.org/packages/0e/35/150383a42d83ec4c7b39f9c50bd68408ecf04c19fc30ea5198fa42e67d9c/scikit_learn-1.9.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:48fefd8eb42bd4eec3e2d348149368ccd6d71987e20c30706a56a24eb86a6e73", size = 9059953, upload-time = "2026-09-10T18:33:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/9f/dd/aa0d738808540f7eaacfab93e01982db8ef1c1c7473ef0ad38193e6aebd1/scikit_learn-1.9.1-cp315-cp315t-macosx_12_0_arm64.whl", hash = "sha256:09f4d73049cd63575157f6b1060e06a8c83a4bd3488dbfaeedf35ccba7aad712", size = 8646602, upload-time = "2026-09-10T18:33:53.23Z" }, + { url = "https://files.pythonhosted.org/packages/7e/cc/687ae4214c2f598906c3b9fa5f86fbaf834b35e20360625528ff1b713f06/scikit_learn-1.9.1-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3da53831534214322d9cb240fa6f390b36cf69eba727a6d4bd3238677630d70", size = 8880366, upload-time = "2026-09-10T18:33:55.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/03/82215cb78ad1c513a4498777571fb28444621ad26ef636287551767b7732/scikit_learn-1.9.1-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caae15634feceafa2612566b109a3082d3293167fac388eedaf77bff66b51983", size = 9129640, upload-time = "2026-09-10T18:33:58.186Z" }, + { url = "https://files.pythonhosted.org/packages/d4/90/4b7af4efd7909a4a0524a9f18457e4eb2eb60616eff2c7627eda8e3cdceb/scikit_learn-1.9.1-cp315-cp315t-win_amd64.whl", hash = "sha256:ffbcbbbb44202fbe9bc64bced25a145759adb9ef010b3d37a8064958ac13df2a", size = 8678083, upload-time = "2026-09-10T18:34:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/31/27/068e484d4b83004302e0d9cfc1faca69bcc010d76fbb66a642446095af1b/scikit_learn-1.9.1-cp315-cp315t-win_arm64.whl", hash = "sha256:800dd22dd87fe97dcea484c24e85dd93cf1734d86bd74e668ad18f7967f4d1b5", size = 8259434, upload-time = "2026-09-10T18:34:02.678Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235, upload-time = "2026-08-21T23:28:50.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/d5/d8eb4e280ddb56a4ab2c6f02ee49b56b23f6e977cf0802fd6d68dbef14f5/scipy-1.18.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:83de5453a7799afc9048b4616bd085cef126e36412f0ea2f6370c36a2a3a51e7", size = 31090936, upload-time = "2026-08-21T23:25:28.686Z" }, + { url = "https://files.pythonhosted.org/packages/2a/49/59ea385dc3a62ff498ddf3cfff7c2b41b0f9f9d3c4122b3f1dcb6d6327fe/scipy-1.18.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:9554bcc6d715ee87a633a3cc8e7703c6628b100dd29cb8a2efc4c0533c7ff729", size = 28725221, upload-time = "2026-08-21T23:25:33.244Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/6b0c288c50942d78193696c9f15f9a0874f5178aa0ddf40f83d9924b3e8d/scipy-1.18.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:011413b7426b75012840e35649e00fe0a2c3bae89fed433876e3a99251572efc", size = 20466839, upload-time = "2026-08-21T23:25:37.516Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e0/54fd3793c729e3b936782f181b59cbb1205bf250ab605a16cb1ba61cdd5e/scipy-1.18.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:88f0e784020649f88ea48c9f5ddfa403bf9205820667c0914740b392035afb82", size = 23089121, upload-time = "2026-08-21T23:25:42.019Z" }, + { url = "https://files.pythonhosted.org/packages/0b/56/030af62bea3cf878e0028515dff78c123b01633606a879b63f42d2db99cc/scipy-1.18.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d3ab0e8c69a17dd3559eab8cbb88f258e285c94d572c2719033f90f83290c89", size = 34053851, upload-time = "2026-08-21T23:25:47.998Z" }, + { url = "https://files.pythonhosted.org/packages/6b/89/2a844506d49651e9aa1af6ef95b6bd8031cb1d5a4375edec6155037e04cf/scipy-1.18.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac0333bdf38309aa3dcbe7e3fa7ea29e7a2c37c6ea306a757b700ded8e4596ad", size = 35329183, upload-time = "2026-08-21T23:25:53.522Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/c7370c3640e92ac9613cbf26cb3f729f9b12ddf1727b55b94b53b24d6f48/scipy-1.18.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:911de823097db8b63f034299d12662db93344e6ffa0b881cbb57748974b70168", size = 35672551, upload-time = "2026-08-21T23:25:59.387Z" }, + { url = "https://files.pythonhosted.org/packages/24/16/ec8536f351421f8bf60a1120930638f83790f4710b8230446aca3d6159d4/scipy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:95298364e251be3e60249facbeeca03631d3bb7584f85879516ec55ac717b81f", size = 37469416, upload-time = "2026-08-21T23:26:05.432Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/d73da0d28f16c45bb9b0a5691b91610b0275c5ef0eb5e43c87cf2dc1bf31/scipy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:78a0d7c918e74a232394117160e7e3db503377572a45bcef8826e4ab8a35feba", size = 37362755, upload-time = "2026-08-21T23:26:11.366Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/e996e4dc74e10e227b1e14db5eaf6608bb6dd33884a64851c38f18dd4249/scipy-1.18.1-cp314-cp314-win_arm64.whl", hash = "sha256:cbf38d043c1aa4ab306e1ada6ab6eddacc3322a20b7af1b30bc93254b366fe09", size = 25036090, upload-time = "2026-08-21T23:26:15.887Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c9/c00213f92309d753b48903e6a451b87eb52ff5b7a16e789d1568bbf221c4/scipy-1.18.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0fcb3c93519f27bb4f0c4b0f7802cdcaca7fcf93267b75edda2e9f4e8a55cbd7", size = 31485550, upload-time = "2026-08-21T23:26:20.776Z" }, + { url = "https://files.pythonhosted.org/packages/74/b2/e3067c487982d4eeab2938928529410370c06fea84a4d3f4925e7d96647d/scipy-1.18.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ddef79fb382df40104a19bb7151b3b23e57c1778fcf857c71ceecd9bd264513f", size = 29174642, upload-time = "2026-08-21T23:26:25.395Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ab/374c9fe2d1ec014e576c781a4b5d8e1ba340e8f6b4638c16f711d2b194f0/scipy-1.18.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0e82073ecc7acc6436fac4b31674109c7e1d3e596789767eda01258a8c9e8123", size = 20916357, upload-time = "2026-08-21T23:26:30.112Z" }, + { url = "https://files.pythonhosted.org/packages/90/38/223915c88a17317cafbf8ca2a42b11c265a9fb1e804aa665544132b5fe8a/scipy-1.18.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8bcf3c1ba5d6456e2effd30fcbd3459b044d683fcdac79a2e6830f0bdf7de487", size = 23482611, upload-time = "2026-08-21T23:26:34.846Z" }, + { url = "https://files.pythonhosted.org/packages/c4/d1/db0948da8ca57a80b36520ef0a768b967d99f3af65f4b6f1bf6362ad4dd4/scipy-1.18.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cfbf154f2ba187f2ed6cce2639efff7d105f1140573642c0161615b6d91d6a87", size = 34143202, upload-time = "2026-08-21T23:26:40.4Z" }, + { url = "https://files.pythonhosted.org/packages/87/53/39d046cc7574ed6acacb6bd5723e220107ece80bff12faaf3efc4ddeede4/scipy-1.18.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1d33a7836f7ddc1993427966a0823468ec41bcbdb1a9f9942d1d7e57f803ba3", size = 35380876, upload-time = "2026-08-21T23:26:46.1Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/32e0e799d875a85ca57d9bde6c78148afcc0e38276df683d95854eadc8c3/scipy-1.18.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4b8bc363b6d65ee2152bec57568e3c52639bb34c46057b09857a307ed5e21d", size = 35770885, upload-time = "2026-08-21T23:26:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/88/2e/f97a666d362fee68b18f41c9c30ed502ca5c98b549749bfcb52a8b74d1eb/scipy-1.18.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:11c423f1049c5755ad4409af52a9ada1cff96fe9b50795d4af3619f292901239", size = 37525424, upload-time = "2026-08-21T23:26:56.751Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d5/a9e765a84654ebba8479a1fd1b059ced1af72b168a3b2a3a46540ea38d20/scipy-1.18.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c24acac1e18912761c4700239bbc1fd32f615af690f1584d49b35859be51324d", size = 37416961, upload-time = "2026-08-21T23:27:01.546Z" }, + { url = "https://files.pythonhosted.org/packages/ee/16/e79e0d1c63ef698879d85439d37e9fb434e3b804e506a6991038d086ebd9/scipy-1.18.1-cp314-cp314t-win_arm64.whl", hash = "sha256:9f2897bf7737392ad0d5213ea7b6add72a4edf5679b3153106aeb88b6507b3b9", size = 25331848, upload-time = "2026-08-21T23:27:05.884Z" }, + { url = "https://files.pythonhosted.org/packages/be/4f/1bd37c883b67163e2ca1f60977a399500e6879c15defecac62831c8d078d/scipy-1.18.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:eb0dfcf4e28a99c12c999744a2ff67c9b06200e20401c7c88186e33552a46331", size = 31091484, upload-time = "2026-08-21T23:27:11.051Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c5/ba929d7feb9b2332f96827c12e0e924b61973b59b4dea383b603372c65ce/scipy-1.18.1-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:30f464bee641fa8e282577c7dce027308403213c6ca8270bba73285c91024bc5", size = 28725057, upload-time = "2026-08-21T23:27:15.9Z" }, + { url = "https://files.pythonhosted.org/packages/a4/19/68f1c50f609d955d230e66d25d02bd3e1e167ec540232135354fb9a4b9e3/scipy-1.18.1-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:1bca3b943fc2567ea49cd02c99abde49da4d5178ec46f624bd8255cda8755beb", size = 20466734, upload-time = "2026-08-21T23:27:20.044Z" }, + { url = "https://files.pythonhosted.org/packages/ef/6d/319fa29b73d1802fa80b32a6eaf3f5be456ef81526da2716a9493bcb5501/scipy-1.18.1-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:c9d18a33309122074ea483dd92dd444189166b8b2ec429fe9ed5ac73c7a0aa23", size = 23089664, upload-time = "2026-08-21T23:27:24.345Z" }, + { url = "https://files.pythonhosted.org/packages/b7/db/30992f9b51a63de671daf3888ffd18378b6cb9ec9f2c972264238ffa7fd6/scipy-1.18.1-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82f201b4c878551d48558337aab270d3c6cca5507b8737c8d8a608d234cccde0", size = 34054035, upload-time = "2026-08-21T23:27:29.409Z" }, + { url = "https://files.pythonhosted.org/packages/91/d4/bf3e735dc0b9d5a8ff45079d2540e17d3aff7a2f0048dd8f552ffd031d2b/scipy-1.18.1-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ac49ea97594532dd44b7136094d35f5440fa06e6d9c6384a74c01764df388c5", size = 35333883, upload-time = "2026-08-21T23:27:34.293Z" }, + { url = "https://files.pythonhosted.org/packages/19/93/12d78ce9f871fe945fca588d32644e6e63f553c2a35c564d73f3b22a3313/scipy-1.18.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:ceb30a00ce7c92d459819443d29ca486d882b83fb6738bdcbb2a1cce94ac5daa", size = 35673124, upload-time = "2026-08-21T23:27:39.059Z" }, + { url = "https://files.pythonhosted.org/packages/70/cd/886219313a1012a48e6ae0ec4f302c837151beb92e1ff0d709ef8fdfc488/scipy-1.18.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f29633129f9fa7e88a3f0fca835de2d030bfc9643f7799e1a0c46cee24d38fc7", size = 37470753, upload-time = "2026-08-21T23:27:44.435Z" }, + { url = "https://files.pythonhosted.org/packages/17/6c/a776888ce618bee54fbde26172f0f46ac1da70d27b63861797fe78e1904b/scipy-1.18.1-cp315-cp315-win_amd64.whl", hash = "sha256:92c14f5bdbfb6216315ce33e78080474082de8b3830122ba97809bfbe65f75c0", size = 37361483, upload-time = "2026-08-21T23:27:49.334Z" }, + { url = "https://files.pythonhosted.org/packages/ab/09/97b651691322ebee97999b017ffc18a15a0b815103844c97e8da9d469731/scipy-1.18.1-cp315-cp315-win_arm64.whl", hash = "sha256:e402cf31eb68f453dbb2d36fc6d722b33f24a55d68b2ae1d92fa6305ca71c298", size = 25035883, upload-time = "2026-08-21T23:27:53.596Z" }, + { url = "https://files.pythonhosted.org/packages/ed/0f/9ec20467bbabd0d44e2a77d0fd3d124f884b4d67df92af82c91d2d6a486f/scipy-1.18.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2a0b02f9fc46f8520330c23d45e6560db7e3a0d927232139427637f98943e11d", size = 31474926, upload-time = "2026-08-21T23:27:57.993Z" }, + { url = "https://files.pythonhosted.org/packages/8a/58/dcb79161e56efbedc50079fcd2f5fe427a0ebb53022eb476aa73c015ad8f/scipy-1.18.1-cp315-cp315t-macosx_12_0_arm64.whl", hash = "sha256:1d73131e358976663dd969e1fb4ed1404b815cd977eaaedc3b3a133ba2d81c35", size = 29164940, upload-time = "2026-08-21T23:28:03.062Z" }, + { url = "https://files.pythonhosted.org/packages/71/d3/1eeea80c817fcb8ef7bd4a05a58824977a0e57a375cfc3d7ea7c911c01ad/scipy-1.18.1-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:bff0b729edd992766136b34e39cc76bc2fad905aa58897ee72a9cd000a6d8443", size = 20906742, upload-time = "2026-08-21T23:28:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/54/46/e59350428b6099301a20128108c995e2eb175a43f383af9a346e38824f9b/scipy-1.18.1-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:10ac20c69d880f77f375db44c22e3e6a644f9fefa291d4cd2fb9790a89fc99fd", size = 23472183, upload-time = "2026-08-21T23:28:12.109Z" }, + { url = "https://files.pythonhosted.org/packages/89/31/cc91623fa98f0621766a0f0aaaadb2c66de74a7ea7e3837164f6e4354260/scipy-1.18.1-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33a834464fdabc0f26a45508df31b3cc5d028e04dbf6c5ed398541418e0a12fe", size = 34130796, upload-time = "2026-08-21T23:28:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3e/8572ef536957ddb8aa81bb4090d9e25f257e3b4e05d97deb54319deb8a3a/scipy-1.18.1-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49023963c193dacee096301452f223ee24d86ec5807f8df93c0f7221d119e305", size = 35374253, upload-time = "2026-08-21T23:28:23.732Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c6/59fdeffb4f1435299f93d9dc8140b43ad2916e6cfc944be6c3041fcec86d/scipy-1.18.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:d84a09d0dad90ba6525d8ac1c2334b33e64bf3ccfe9e841f02feb867a22681e4", size = 35758543, upload-time = "2026-08-21T23:28:29.431Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d9/135be205d9de8783193aff9cc3bf483a03a38e4b29432c954e8cb66ac14e/scipy-1.18.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:179ce34a8d0fe273d8883ba59e17e052247d08973dfcb743ca52bb1cce2d60b0", size = 37521946, upload-time = "2026-08-21T23:28:35.245Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a2/5b7d5270621ab7cfa3f7766067bf95dc360b5efb6394694e8143b4156e2b/scipy-1.18.1-cp315-cp315t-win_amd64.whl", hash = "sha256:5632e3ae3d09197c446310cd5187de63e28448ce22f0f67b2b93d97503c0c230", size = 37408295, upload-time = "2026-08-21T23:28:40.724Z" }, + { url = "https://files.pythonhosted.org/packages/63/ad/741c19fcb66755ff953daf9243af8480e4bf3d7fbe57583c178c7d2b6b51/scipy-1.18.1-cp315-cp315t-win_arm64.whl", hash = "sha256:eda632a7981f69730d6281f451db9c1c370993a2c0d7ddb43e2a809a2862b83a", size = 25319710, upload-time = "2026-08-21T23:28:45.713Z" }, +] + +[[package]] +name = "sentence-transformers" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "tokenizers" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "transformers" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/ef/87681678d0aa91a1e77f0ffdd2e5d900aaf358aac644dd14bead8d95ec4c/sentence_transformers-6.0.1.tar.gz", hash = "sha256:1c3b8d9403f87ad0c879638554f36cc85744f38a6a70d150fd7e964ca0e9935b", size = 575395, upload-time = "2026-08-31T07:50:43.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/ab/dc6031faf1b6bba08076fbf457b21a5c51ba7928a77916915e093c16b8c7/sentence_transformers-6.0.1-py3-none-any.whl", hash = "sha256:b8888d72c707ba33c63aa30845850702dd5acadf1dd0d051436380bcebe4fd0f", size = 739832, upload-time = "2026-08-31T07:50:41.635Z" }, +] + +[[package]] +name = "setuptools" +version = "84.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/dc/6c58154c1c65f758ea979e7139cb76993a9cfc662d14e9be3c4a667cfb77/threadpoolctl-3.7.0.tar.gz", hash = "sha256:61348cfb77d53b9242e0017029244b559b810c142ced65b4e21eeca1843959a7", size = 31961, upload-time = "2026-09-15T15:46:20.263Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/3f/f88a53f60a472b46f4023f56d204dd7de33d34c5d2acbfa0d70a674e639e/threadpoolctl-3.7.0-py3-none-any.whl", hash = "sha256:cd8b60b5641b45c67bbf73c64c843235fc2d8a480c87389f52f5dbee893b86be", size = 26362, upload-time = "2026-09-15T15:46:19.168Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/1e/bc6587c5ab643b2e17776cace9070a2ae73549c86bffac9934a600bf3c31/tokenizers-0.23.2.tar.gz", hash = "sha256:7f0f085686b9de0d0079e6f874ae053600db64c5d13049e0bbc0119926d25aac", size = 385745, upload-time = "2026-09-03T08:55:42.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/ed/8a443528baa6fac8dfe8c3b75b038c63ac92bb539bcabe311e227c718173/tokenizers-0.23.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:85a9a357a3764aecc904ee76bdaf8cf1ad8e5a67a1b929a487c4a39b49ed0e90", size = 3148852, upload-time = "2026-09-03T08:55:30.874Z" }, + { url = "https://files.pythonhosted.org/packages/67/49/22da045a91732384d3a3771816bf188dc5a1f702c32e635afa7c679c0bef/tokenizers-0.23.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:986670e43691469dcee610ea0f846f91a8f84e91fc6f7a48d4c064414c0ec2bf", size = 3101593, upload-time = "2026-09-03T08:55:28.587Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4d/8f569ed49372a3ed8e57099bd515055fd48d7c95912c4307cda6973c2168/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a37039b5dfc4af84eb3ef0a92f4307e28936c8f9adccba2629d36f652e9bf7a2", size = 3516830, upload-time = "2026-09-03T08:55:14.741Z" }, + { url = "https://files.pythonhosted.org/packages/2a/de/e2f14c8919d5bf51874051d00d6c7b7e0e8bde6c6a2dbeddda7f642896ff/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b7e37ba198f24150f523e1242e83c4970de4a525480586be5dcc24d9add32c5", size = 3407975, upload-time = "2026-09-03T08:55:16.842Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bd/93c69152d02ef06ce47aed8b2bf4952dcf733c935a62791873932b2934d9/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43e4f2071e3cc8d5d86421c874aebc82659bb51a68bcdef5a0da75ee89511ccb", size = 3748165, upload-time = "2026-09-03T08:55:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b7/56b84b80bc96942bba8eb23751a9e8a1fce4faaf4390425e7083f721c98c/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:325fee2e0418a9dc6c9ecf736a5f5f0db7875183ace9549ae339da76f7a1fbb7", size = 4024165, upload-time = "2026-09-03T08:55:18.806Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/0175e216f005c2fe08238292663aa41e4c802b216e71047a69a0e9fc6fa3/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:950d7c9426fa72406a0ffeacdbc0bb9985f5db20eb8b263f29c79aaf83105703", size = 3591899, upload-time = "2026-09-03T08:55:22.752Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ca/ca6b93c7820df123b2662a9469e8facc826ccc94e98fdd0d615f6431e73a/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41c2f84d172449b4dadb9cdc508e3e364076613c35b16e76ecfe47a60d1e3305", size = 3386843, upload-time = "2026-09-03T08:55:26.584Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a4/4f9106d317b14a80aefea9f0e3a8d07ef25f856a7607eb7f5ab894281fcb/tokenizers-0.23.2-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:12f0835dc2ee694746a76adf7b1567d4346a4a502ebe93fb1f5f80ea49799b78", size = 3577314, upload-time = "2026-09-03T08:55:20.825Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6a/1552b70fb0d9ab074fd3fc961435d01364e79c9058481822c3af6e8d402c/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb2f9c8a24da020ea8c11a01a19c1c2547912d92121ae4a01cfbca46125dee40", size = 9967367, upload-time = "2026-09-03T08:55:33.188Z" }, + { url = "https://files.pythonhosted.org/packages/06/01/3ccb3a956c7528b2507b8a9714155c4baf86af593039db6ea375dd0c96c3/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f486f402f6f9abee5bb032553736813af0c710a86b2e0ca592634c55cea1f835", size = 9811886, upload-time = "2026-09-03T08:55:35.642Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/7038e612d48bda1599457f712f6bd3854eae1a9dc9c13aa47f835349db48/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:bef235815a067b2648caf6dcc7a71091b0b0fff9ee8057f6451eb9335fae52ef", size = 10146224, upload-time = "2026-09-03T08:55:38.391Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d8/8e9e4e0b287a338d8f88976729628c9d22e8a54cfaf9777018a7f7cb58a0/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5c56bda1511921587789163e524d196ed8284174ac23abd7685d5ea8da6c4718", size = 10256304, upload-time = "2026-09-03T08:55:40.977Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1f/c79a01f671a49728ebb0b61f7ff9ea45663b66cab40bc0858e9859b25c16/tokenizers-0.23.2-cp310-abi3-win32.whl", hash = "sha256:debf978920d93ba9c219bd67cc4bbfaf912c9039e41e7a28b91ec15e3728c95a", size = 2592809, upload-time = "2026-09-03T08:55:48.02Z" }, + { url = "https://files.pythonhosted.org/packages/db/f7/0a69ac6b82dbccf3f71add938a161c497952749294b8dd6dfe03a819dc40/tokenizers-0.23.2-cp310-abi3-win_amd64.whl", hash = "sha256:2e96f5699d5249c9c64aa8412e044f727aae3a4098cf830f9901ec1afc361cde", size = 2863236, upload-time = "2026-09-03T08:55:46.193Z" }, + { url = "https://files.pythonhosted.org/packages/d7/b0/dee84cb44175be1b4c35bd2f770727494e78f0bb38e571a623ade94dbebb/tokenizers-0.23.2-cp310-abi3-win_arm64.whl", hash = "sha256:e49c394456dd9985787fec76132438ba3fb8911f857b1bf3d40119f9292d41aa", size = 2729352, upload-time = "2026-09-03T08:55:44.345Z" }, +] + +[[package]] +name = "torch" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/90/1241e7db5ccc2455f8735bd6b1becfad39916206ad18001c4c0014d139e2/torch-2.14.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:860423e970f2ce02c4476e8e2d1350131b1c5c5a5e4912180e78b50b53241efa", size = 127321431, upload-time = "2026-09-02T13:44:38.236Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/bbeba65e3fbb4b8f61ea19cf4ebea9f4268cc6fe64e6f7d38bfb6cc152eb/torch-2.14.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:b985c7defeb8d28691b7513ebaecc64946c5f68ec770e9f4ddba39688864f46a", size = 454027631, upload-time = "2026-09-02T13:45:43.73Z" }, + { url = "https://files.pythonhosted.org/packages/50/75/8d2b9a7e724759470c209489b79260cac537f091cc9aac8001c8d2bc845c/torch-2.14.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b2cd92bce63d40bf6fc2e5d840fc2f0063bd180a242daa761cb6088cc2f46e27", size = 554623549, upload-time = "2026-09-02T13:46:04.252Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/a5475c00b0555e686333e6b4036f2213e7cbea021a772ce6f9ced4dcbd2f/torch-2.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:44b044b9f6f633d982839422a57433d6a1da520037fd88e0c8a47efde589b3b8", size = 124110863, upload-time = "2026-09-02T13:45:12.764Z" }, + { url = "https://files.pythonhosted.org/packages/a5/49/bbaee76337742a42d2c5b0296ea62252567050e1d821bc2283b2e72ba6fb/torch-2.14.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:553aec938d37d77b783bcf801e638cad068870e7643c47eadac21eb180f551ed", size = 127653463, upload-time = "2026-09-02T13:45:17.1Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/6cc7a511bab384fbe8f4b8ddeecdd22e724b2162d6f15076f07a0a7ef15b/torch-2.14.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cd8cd8f714d511ccdca907282d1da3d9be8322d4e1520b9c3bce39a5c1318a4b", size = 454009927, upload-time = "2026-09-02T13:46:19.637Z" }, + { url = "https://files.pythonhosted.org/packages/b6/da/0f04fe15fd05bf3f613a359b14acb44c35ae06fe69da0aefa8b5cdb4f9f9/torch-2.14.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d2526f71e6638133b97b3cd2881ece3df521460a4727030e8ae2a72a7d3ae31d", size = 554580530, upload-time = "2026-09-02T13:46:34.818Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c3/72ae1f02747b1f012e1975743e48cd608f83095d7f9ce58de78b79248b35/torch-2.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:731784e3914843c6bcc7aba3987ff7610ac57dbbc816a5d6b9b62e04c240a641", size = 124400194, upload-time = "2026-09-02T13:45:53.555Z" }, +] + +[[package]] +name = "tqdm" +version = "4.70.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz", hash = "sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4", size = 171846, upload-time = "2026-09-11T07:25:16.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199, upload-time = "2026-09-11T07:25:14.599Z" }, +] + +[[package]] +name = "transformers" +version = "5.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/9e/750649904a065007a838981785b2bd8d9ff26154c6c341ac67d0b7f82c68/transformers-5.17.0.tar.gz", hash = "sha256:a153be279169b55b92d8000bf4af294aed684503d091cca7804da2dd8a9de000", size = 9817878, upload-time = "2026-09-09T15:39:56.886Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", hash = "sha256:78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", size = 12295140, upload-time = "2026-09-09T15:39:53.746Z" }, +] + +[[package]] +name = "triton" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/59/bf0e9493118bb353ab59a5d6a65db3618d9b314417cc1459f0121e0ec5c9/triton-3.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f6b48d0591929a3867973acac3dccd4e058585f91bfb41022de496c9ffab304", size = 226488654, upload-time = "2026-08-28T16:08:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/93/d9/08c75f3459f19ad00425b564058e40efa4bcd79b816064cf27499303ea42/triton-3.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:387dae4cb0089a7b6ba1a428ae0782b65c4c58f57d94617cb22ca8593d8ccbca", size = 247972313, upload-time = "2026-08-28T15:56:14.007Z" }, + { url = "https://files.pythonhosted.org/packages/7c/34/429c5592181cfb7361a0a8e0bff218e7224b726709d75da2472b3e819f70/triton-3.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b84e7d512490ba529111260fa6f7cad8b254a6bb5fbdf41d5ef9a5e57f52d0a", size = 226591133, upload-time = "2026-08-28T16:09:02.271Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d1/aa8a3e935c37efee7945984fdb64d7e0851bf6d920afd97b2d21f9d23360/triton-3.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74217bb56ed8692759227758e4c4b3bd2d608a209c1a7a081bf361fb4c2c1bf9", size = 248077577, upload-time = "2026-08-28T15:56:24.94Z" }, +] + +[[package]] +name = "typer" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] diff --git a/RAG/rename_db_rag_embeddings.py b/RAG/rename_db_rag_embeddings.py deleted file mode 100644 index b91318f..0000000 --- a/RAG/rename_db_rag_embeddings.py +++ /dev/null @@ -1,25 +0,0 @@ -from dotenv import load_dotenv -import os -from pymongo import MongoClient - -load_dotenv() - -MONGO_URI = os.getenv("MONGO_URI") -DB_NAME = os.getenv("MONGO_DB") -COLLECTION_NAME = os.getenv("COLLECTION_NAME") - -if not MONGO_URI: - raise ValueError("MONGO_URI not found in environment variables.") - -client = MongoClient(MONGO_URI) -db = client[DB_NAME] -collection = db[COLLECTION_NAME] - -original_field = "embedding" -new_field = "_vector" -result = collection.update_many( - {original_field: {"$exists": True}}, - {"$rename": {original_field: new_field}} -) - -print(f"Modified {result.modified_count} documents.") diff --git a/backend/.python-version b/backend/.python-version new file mode 100644 index 0000000..a6d9ada --- /dev/null +++ b/backend/.python-version @@ -0,0 +1 @@ +3.14.5 diff --git a/backend/Dockerfile b/backend/Dockerfile index 9a390d6..d25a04a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,16 +1,20 @@ -# Use a lightweight Python base image (includes PyTorch runtime) -FROM pytorch/pytorch:2.9.0-cuda12.8-cudnn9-runtime +FROM python:3.14.5-slim -WORKDIR /app +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ -# Install dependencies first for caching -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt +WORKDIR /backend -# Copy app code -COPY . . +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 + +COPY pyproject.toml uv.lock ./ + +RUN uv sync --locked --no-install-project --no-dev + +COPY app ./app + +ENV PATH="/backend/.venv/bin:$PATH" EXPOSE 5000 -# Run Gunicorn with 2 workers + sane timeouts -CMD ["gunicorn", "-w", "2", "-b", "0.0.0.0:5000", "--timeout", "120", "--graceful-timeout", "30", "--max-requests", "200", "--max-requests-jitter", "50", "app:app"] \ No newline at end of file +CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT:-5000} --workers 1 --timeout 120 app.app:app"] \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index 56d7dd6..09450b4 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,234 +1,477 @@ -# SLRmentor App Backend +# SLRmentor Backend -This is the backend for the **SEALL** application — a tool for generating and refining academic search strings and inclusion/exclusion criteria using AI (Gemini & GPT), MongoDB, and Flask. +[![Latest Docker Image Version](https://img.shields.io/docker/v/seallabuofc/slrmentor-backend?sort=semver&label=Latest%20Docker%20Version)](https://hub.docker.com/r/seallabuofc/slrmentor-backend/tags) ---- +[Docker Hub repository](https://hub.docker.com/r/seallabuofc/slrmentor-backend) -## Requirements +SLRmentor is a SEALL application that supports systematic literature reviews with AI-assisted academic search string generation and conversion, inclusion/exclusion criteria refinement, and research guidance. -Make sure you have the following installed: +The backend uses **Flask**, **MongoDB Atlas**, **Google Gemini**, and **OpenAI**. Retrieval-Augmented Generation (RAG) uses MongoDB Atlas Vector Search with Automated Embedding, so the backend does not need to host an embedding model locally. -- [Python](https://www.python.org/downloads/) -- [Anaconda](https://www.anaconda.com/download) -- [MongoDB Community Edition](https://www.mongodb.com/try/download/community) -- Create .env file --- -## Project Structure +## 1. Requirements + +### Local development + +- [Python](https://www.python.org/downloads/) **3.14.5 or later**, as declared in `pyproject.toml`. +- [uv](https://docs.astral.sh/uv/) for Python environment and dependency management. +- Access to a MongoDB Atlas cluster with the required collections and Vector Search index. +- API credentials for the LLM providers you plan to use. + +For Docker-based execution, install [Docker](https://docs.docker.com/get-docker/). You do not need Conda or a local MongoDB Community Server for the Atlas-based configuration. + +## 2. Project Structure + +```text +BackendRefactor/ +├── .env +├── .dockerignore +├── .gitignore +├── Dockerfile +├── README.md +├── pyproject.toml +├── uv.lock +└── app/ + ├── __init__.py + ├── app.py + └── helpers/ + ├── RAG/ + │ └── RetrieverManager.py + ├── db/ + │ └── mongodbhelpers.py + ├── llm/ + │ ├── promptBuilder.py + │ ├── llmPromptingUtils.py + │ ├── llmReturnFormat.py + │ └── prompts/ + ├── cryptographic_helpers.py + └── scholarlyapi.py +``` + +Commands in this README assume you are in the **project root**, where `pyproject.toml` and `Dockerfile` are located. + +## 3. Environment Configuration +Create `.env` in the backend folder: + +```env +# MongoDB Atlas Configuration +MONGO_URI=mongodb+srv://your-username:your-password@your-cluster.mongodb.net/ +MONGO_USERNAME=your-mongodb-username +MONGO_USERNAME_PASSWORD=your-mongodb-password +MONGO_DB_NAME=your-database-name +MONGO_DB_RAG_COLLECTION_NAME=your-rag-collection-name +VECTOR_SEARCH_INDEX=autoembed_index + +# LLM API Keys +GPT_API_KEY=your-openai-api-key +GEMINI_API_KEY=your-gemini-api-key + +# Other Configurations +RAG_doc_limit=5 +message_history_limit=30 +chat_hash_length=8 +hash_byte_length=16 ``` -SEALL/ -├── backend/ -│ ├── .env -│ ├── app.py -│ ├── environment.yml -│ └── helpers/ -│ ├── llm/ -│ ├── xploreapi/ -│ └── ... + +Replace the placeholders with your actual configuration. **Do not add surrounding quotation marks or spaces around `=` in this `.env` file.** Docker's `--env-file` can include literal quotation marks in environment variable values, which causes PyMongo to reject a quoted `MONGO_URI` with `Invalid URI scheme`. The unquoted format above works with both Docker's `--env-file` and `python-dotenv` during local Flask development. + +The current `app.py` and `RetrieverManager.py` use `MONGO_URI` directly; the separate username/password variables are retained from the project's environment template. If your credentials include reserved URL characters, encode them correctly in `MONGO_URI`. + +The backend includes Gemini-to-OpenAI fallback behavior. Configure both API keys for fallback to work; check individual routes if you intend to use only one provider. + +| Variable | Purpose | +|---|---| +| `MONGO_URI` | MongoDB Atlas connection string | +| `MONGO_USERNAME` | Optional separate MongoDB username setting retained in the project configuration | +| `MONGO_USERNAME_PASSWORD` | Optional separate MongoDB password setting retained in the project configuration | +| `MONGO_DB_NAME` | Database name | +| `MONGO_DB_RAG_COLLECTION_NAME` | Collection containing RAG documents | +| `VECTOR_SEARCH_INDEX` | Atlas Vector Search index name | +| `GPT_API_KEY` | OpenAI API key | +| `GEMINI_API_KEY` | Gemini API key | +| `RAG_doc_limit` | Maximum RAG documents to retrieve | +| `message_history_limit` | Maximum recent messages used in conversation context | +| `chat_hash_length` | Chat hash length configuration | +| `hash_byte_length` | Hash byte length configuration | + +**Security:** Keep `.env` in both `.gitignore` and `.dockerignore`. Do not commit secrets or copy `.env` into the Docker image. Other helper modules may require additional environment variables. + +## 4. Local Development with uv + +Install uv following the [official instructions](https://docs.astral.sh/uv/getting-started/installation/), then verify: + +```bash +uv --version ``` ---- +Install dependencies from the lockfile: -## .env File +```bash +uv sync --locked +``` -Create a file called `.env` inside the `backend/` directory with the following content: +Run the Flask development server: -```env -GPT_API_KEY="your-gpt-api-key" -GEMINI_API_KEY="your-gemini-api-key" -ENCRYPTION_KEY="your-encryption-key" -MONGO_URI="mongodb://localhost:27017/seall" -MONGO_USER="sealluser" -MONGO_PASSWORD="securepassword123" -MONGO_DATABASE="SEALLDB" -``` - -Change the placeholder values to your actual keys, encryption key, and MongoDB credentials if different. -You will need to aquire the AI API keys by yourself. You only really need one, either Gemini free tier key or the OPENAI key. -Both are recommended. ---- +```bash +uv run flask --app app.app run --debug +``` + +Open . The root endpoint returns: + +```text +Server is live ;) +``` + +You do not need to activate `.venv` when using `uv run`. Use the Flask development server only for local development. + +## 5. MongoDB Atlas and RAG + +The backend performs semantic retrieval using **MongoDB Atlas Vector Search with Automated Embedding**: + +1. A Flask route receives a query and passes it to `RetrieverManager`. +2. MongoDB Atlas generates the query embedding remotely. +3. Atlas Vector Search returns the most relevant document chunks. +4. The backend formats the retrieved text and includes it in the LLM prompt. + +No local embedding model is loaded by this workflow. + +### Vector Search index -## Setting Up the Conda Environment +The project uses the following index configuration: -Open a terminal and navigate to the `backend/` directory: +```json +{ + "fields": [ + { + "modality": "text", + "model": "voyage-4-lite", + "numDimensions": 256, + "path": "content", + "similarity": "cosine", + "type": "autoEmbed" + } + ] +} +``` + +Create the index on the collection designated by `MONGO_DB_NAME` and `MONGO_DB_RAG_COLLECTION_NAME`. Its name must match `VECTOR_SEARCH_INDEX`. Verify that the selected Automated Embedding configuration is supported by your Atlas deployment. + +### Retrieval interface + +```python +rm.get_relevant_documents(query) +``` + +This method returns native Python dictionaries rather than LangChain document objects. Results may include `content`, `paper_id`, `source`, `page`, `chunk_index`, and `score`. The `RAG_doc_limit` setting controls the maximum number of documents retrieved. + +Ensure the Atlas cluster is reachable, its user has the necessary permissions, and the index is ready before testing RAG routes. + +## 6. API Endpoints + +| Endpoint | Method | Description | +|---|---|---| +| `/` | GET | Server health check | +| `/createchat` | POST | Create a chat session | +| `/getchathistory` | POST | Retrieve search string chat history | +| `/getcriteriachathistory` | POST | Retrieve criteria chat history | +| `/getmentorchathistory` | POST | Retrieve mentor chat history | +| `/getsystemcontext` | POST | Retrieve system context | +| `/setsystemcontext` | POST | Update system context | +| `/prompt` | POST | Generate or refine academic search strings | +| `/conversionformats` | GET | List supported search string formats | +| `/convertsearchstring` | POST | Convert a search string into another format | +| `/criteria` | POST | Generate or refine inclusion/exclusion criteria | +| `/mentor` | POST | Provide AI-assisted SLR guidance | +| `/ragquery` | POST | Query retrieved RAG documents without an LLM call | + +### Example: test RAG retrieval + +Send a `POST` request to `http://127.0.0.1:5000/ragquery` with JSON: + +```json +{ + "query": "What is a systematic literature review?" +} +``` + +The response contains the query, the number of results, and the retrieved document data. This endpoint tests retrieval independently of LLM generation. + +## 7. Docker Configuration + +The Dockerfile uses Python 3.14.5 slim, uv, and Gunicorn: + +```dockerfile +FROM python:3.14.5-slim + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +WORKDIR /backend + +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 + +COPY pyproject.toml uv.lock ./ + +RUN uv sync --locked --no-install-project --no-dev + +COPY app ./app + +ENV PATH="/backend/.venv/bin:$PATH" + +EXPOSE 5000 + +CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT:-5000} --workers 1 --timeout 120 app.app:app"] +``` + +The server listens on port 5000 unless `PORT` is set to a different value. The Dockerfile does **not** include the `.env` file; configure variables when starting the container. + +## 8. Build and Run Docker Locally + +Build the image: ```bash -cd backend +docker build -t slrmentor-backend . ``` -Create the environment from the existing `environment.yml` file: +Run it with the environment file: ```bash -conda env create -f environment.yml +docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env slrmentor-backend ``` -Activate the environment: +Although `.env` is excluded by `.dockerignore`, `docker run --env-file .env` still reads it from your local machine at **runtime**. The `.env` file must use unquoted `KEY=value` entries, as shown in Section 3. If you change `.env`, stop and remove the existing container, then create a new container with `--env-file .env`; **rebuilding the image is not required** for environment-only changes. + +Verify: ```bash -conda activate seallapp +docker ps +docker logs slrmentor-backend ``` -Install heavier packages from `requirements.txt` file: +Then open . + +Stop and remove the container: ```bash -pip install --no-cache-dir -r requirements.txt +docker stop slrmentor-backend +docker rm slrmentor-backend ``` ---- +## 9. Publish a Docker Image to Docker Hub + +**Docker Hub repository:** [seallabuofc/slrmentor-backend](https://hub.docker.com/r/seallabuofc/slrmentor-backend) + +**Latest published version:** -# Running MongoDB +[![Latest Docker Image Version](https://img.shields.io/docker/v/seallabuofc/slrmentor-backend?sort=semver&label=Latest%20Docker%20Version)](https://hub.docker.com/r/seallabuofc/slrmentor-backend/tags) -You must have MongoDB running **before** starting the Flask app. Here's how: +The badge queries Docker Hub through Shields.io and updates as its cache refreshes. It shows the highest semantic-version tag recognized by the badge service. It does **not** guarantee that the `latest` tag points to the same version. -### 🔸 Linux (Ubuntu/Debian) -You can figure out how to install it for your distro.... -Start MongoDB using systemd: +Replace **`vX.X`** below with your new release version (for example, `v2.3`). + +### 1. Build the versioned image ```bash -sudo systemctl start mongod +docker build -t seallabuofc/slrmentor-backend:vX.X . ``` -Check status with: + +### 2. Tag the image as latest + +```bash +docker tag seallabuofc/slrmentor-backend:vX.X seallabuofc/slrmentor-backend:latest ``` -sudo systemctl status mongod + +### 3. Test the image locally + +```bash +docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:vX.X ``` ---- -### 🔸 Windows +Check logs: -1. **Install MongoDB** from the official MSI installer: - https://www.mongodb.com/try/download/community +```bash +docker logs slrmentor-backend +``` -2. By default, MongoDB is installed as a **Windows service** and starts automatically. - If it's not running, open **Command Prompt as Administrator** and run: +When finished: - ```cmd - net start MongoDB - ``` +```bash +docker stop slrmentor-backend +docker rm slrmentor-backend +``` -3. If you prefer to run it manually (not as a service), use: +If a container named `slrmentor-backend` already exists, stop and remove it before starting the test container. - ```cmd - "C:\Program Files\MongoDB\Server\7.0\bin\mongod.exe" --dbpath C:\data\db - ``` +### 4. Log in to Docker Hub - > Replace `7.0` with your installed version, and make sure `C:\data\db` exists. - You can create it with: +```bash +docker login +``` - ```cmd - mkdir C:\data\db - ``` +### 5. Push the versioned image and latest tag ---- +```bash +docker push seallabuofc/slrmentor-backend:vX.X +docker push seallabuofc/slrmentor-backend:latest +``` + +Publishing an image does not automatically update running containers on other machines. -## Running the Flask App +## 10. Run from Docker Hub on Another Machine (Including AWS EC2) -From the activated Conda environment in the `backend/` directory, run: +Ensure Docker is installed and the target machine has an `.env` file containing its runtime credentials. MongoDB Atlas must allow connections from the deployment environment. + +### 1. Stop and remove the existing container ```bash -python -m flask run +docker stop slrmentor-backend +docker rm slrmentor-backend ``` -If everything is working, you’ll see something like: +Skip these commands on the first deployment. + +### 2. Pull the image tagged latest +```bash +docker pull seallabuofc/slrmentor-backend:latest ``` -* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + +The `latest` tag is maintained by the publisher. For reproducible deployment and rollbacks, prefer an explicit version tag such as `vX.X` (replaced with an actual release number). + +### 3. Run without a memory limit + +```bash +docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:latest ``` -You can visit [http://127.0.0.1:5000/](http://127.0.0.1:5000/) to check if the server is live. +### Or run with a 1 GiB memory limit ---- +```bash +docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env --memory=1g --memory-swap=1g seallabuofc/slrmentor-backend:latest +``` -## API Endpoints Overview +Choose **one** of the two `docker run` commands, not both. Adjust memory according to observed resource usage. -| Endpoint | Method | Description | -|-----------------------------|--------|------------------------------------------| -| `/` | GET | Test if server is live | -| `/createchat` | POST | Creates a new chat session | -| `/getchathistory` | POST | Retrieves search string chat history | -| `/prompt` | POST | Sends a message and gets LLM response. This is for search strings | -| `/conversionformats` | GET | Lists available search string formats | -| `/convertsearchstring` | POST | Converts search string to another format | -| `/criteria` | POST | Generates/refines inclusion criteria | -| `/getcriteriachathistory` | POST | Retrieves criteria chat history | +### 4. Verify the deployment ---- +```bash +docker ps +docker logs -f slrmentor-backend +``` -## 🛠 Notes +To see which image reference was used to create the running container: -- MongoDB must be running **before** starting the Flask app. -- The `.env` file is critical — missing keys will cause the app to fail. -- +```bash +docker inspect slrmentor-backend --format '{{.Config.Image}}' +``` ---- +On AWS EC2, configure the instance's security group and any reverse proxy to expose only necessary traffic; use HTTPS for public deployments. -## ✅ Example Workflow +## 11. Manage Dependencies with uv -1. Start MongoDB -2. Navigate to backend: `cd backend` -3. Activate Conda env: `conda activate seallapp` -4. Run: `python -m flask run` -5. Access at: `http://127.0.0.1:5000/` +Add a package: ---- -## Running backend with docker, and Deployment to dockerhub +```bash +uv add package-name +``` +Remove a package: -1️⃣ Build the image from the current directory in the backend +```bash +uv remove package-name +``` -`docker build -t seallabuofc/slrmentor-backend:v2.2 .` +Sync dependencies: -Optionally, also tag it as latest: +```bash +uv sync +``` -`docker tag seallabuofc/slrmentor-backend:v2.2 seallabuofc/slrmentor-backend:latest` +Update the lockfile if necessary: +```bash +uv lock +``` -2️⃣ Run the container with the .env file to test if needed +Commit `pyproject.toml` and `uv.lock` together after dependency changes. Rebuild and republish the Docker image when application code or dependencies change. -`docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:v2.2` +The native RAG retriever does not require a locally hosted embedding model or LangChain for its retrieval path. -stop container: `docker stop slrmentor-backend` +## 12. Troubleshooting -3️⃣ Log in to Docker Hub (if not already) +### Flask fails to start -`docker login` +Check Python compatibility, dependencies, `.env`, and the application package layout: -4️⃣ Push the new image to Docker Hub +```bash +uv sync --locked +uv run flask --app app.app run --debug +``` -Push the version tag: +### MongoDB connection fails -`docker push seallabuofc/slrmentor-backend:v2.2` +Check the `MONGO_URI`, database user permissions, Atlas cluster availability, and Atlas network access settings. -`docker push seallabuofc/slrmentor-backend:latest` +### RAG retrieval fails +Check that `VECTOR_SEARCH_INDEX` matches the Atlas index, the correct collection is selected, the index uses the `content` field, Automated Embedding is configured, and `RAG_doc_limit` is positive. Test `/ragquery` separately from LLM requests. -6️⃣ Pull the updated image on other machines +### LLM requests fail -If you want to run this updated image elsewhere: +Check provider API keys, quotas, rate limits, and logs for authentication or timeout errors. -`docker pull seallabuofc/slrmentor-backend:latest` +### Docker container exits immediately -`docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:latest` +```bash +docker logs slrmentor-backend +``` +Verify that the runtime `.env` file was passed using `--env-file`. -## Run App from Docker hub on other machines from docker hub (includes ec2) +### Docker build fails after dependency edits -Stop any currently running containers: +```bash +uv lock +docker build -t slrmentor-backend . +``` -`docker stop slrmentor-backend` +## 13. Quick Start -Remove container: +### Local development -`docker rm slrmentor-backend` +```bash +uv sync --locked +uv run flask --app app.app run --debug +``` -Pull latest image build: +### Docker on your computer -`docker pull seallabuofc/slrmentor-backend:latest` +```bash +docker build -t slrmentor-backend . +docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env slrmentor-backend +docker logs slrmentor-backend +``` -run with no memory limit: +### Update an existing Docker Hub deployment -`docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:latest` +```bash +docker stop slrmentor-backend +docker rm slrmentor-backend +docker pull seallabuofc/slrmentor-backend:latest +docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:latest +``` + +For a first-time deployment, skip stop/remove. For production, consider using an explicit numbered release tag and a restart policy. + +--- -with memory limit(1gib example): +## Notes -`docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env --memory=1g --memory-swap=1g seallabuofc/slrmentor-backend:latest` \ No newline at end of file +- MongoDB Atlas hosts query embeddings and vector retrieval; the backend does not load a local embedding model. +- The application runs with Flask during local development and Gunicorn inside the production Docker image. +- `.env` is excluded from the Docker build but passed to the container with `--env-file`. +- The Docker Hub badge displays the latest recognized numbered version automatically, subject to caching. +- Publishing commands use `vX.X` as a placeholder; deployment examples use the publisher-maintained `:latest` tag. +- Existing containers must be recreated to run an updated image. diff --git a/backend/app.py b/backend/app/app.py similarity index 88% rename from backend/app.py rename to backend/app/app.py index 4181fc8..01eb8de 100644 --- a/backend/app.py +++ b/backend/app/app.py @@ -4,13 +4,13 @@ from flask_cors import CORS from flask_pymongo import PyMongo import os, json, datetime -import helpers.scholarlyapi as sa #This is for querying and getting paper abstracts -import helpers.cryptographic_helpers as ch #Generating chat hashes -from helpers.llm.promptBuilder import Prompt #for building prompts -import helpers.llm.llmPromptingUtils as pu #for actually calling the AI API -from helpers.RAG.RetrieverManager import RetrieverManager -from helpers.db.mongodbhelpers import MongoHelper -from helpers.llm.llmReturnFormat import Response +import app.helpers.scholarlyapi as sa #This is for querying and getting paper abstracts +import app.helpers.cryptographic_helpers as ch #Generating chat hashes +from app.helpers.llm.promptBuilder import Prompt #for building prompts +import app.helpers.llm.llmPromptingUtils as pu #for actually calling the AI API +from app.helpers.RAG.RetrieverManager import RetrieverManager +from app.helpers.db.mongodbhelpers import MongoHelper +from app.helpers.llm.llmReturnFormat import Response # from google.genai.types import HttpOptions @@ -23,17 +23,16 @@ mh = MongoHelper(mongo) CORS(app) -hash_byte_length = 16 -chat_hash_length = 8 -message_history_limit = 30 -RAG_doc_limit = 30 +hash_byte_length = int(os.getenv("hash_byte_length", 16)) +chat_hash_length = int(os.getenv("chat_hash_length", 8)) +message_history_limit = int(os.getenv("message_history_limit", 30)) gemini_key = os.getenv('GEMINI_API_KEY') gpt_key = os.getenv('GPT_API_KEY') rm = RetrieverManager( mongo_uri=os.getenv("MONGO_URI"), - namespace="SLRMentor.document_rag", - index_name="vector_index", - top_k=RAG_doc_limit + namespace= os.getenv("MONGO_DB_NAME")+ "." + os.getenv("MONGO_DB_RAG_COLLECTION_NAME"), + index_name=(os.getenv("VECTOR_SEARCH_INDEX")), + top_k=int(os.getenv("RAG_doc_limit")) ) @@ -111,8 +110,7 @@ def create_chat(): print(e) return_request["message"] = str(e) - finally: - return jsonify(return_request), status_code + return jsonify(return_request), status_code @app.route("/getsystemcontext", methods=['POST']) def get_system_context(): @@ -150,8 +148,7 @@ def get_system_context(): status_code = 500 return_request["message"] = str(e) - finally: - return jsonify(return_request), status_code + return jsonify(return_request), status_code @app.route("/getchathistory", methods=['POST']) def get_chat_history(): @@ -193,8 +190,7 @@ def get_chat_history(): status_code = 500 return_request["message"] = str(e) - finally: - return jsonify(return_request), status_code + return jsonify(return_request), status_code @app.route("/getcriteriachathistory", methods=['POST']) def get_criteria_chat_history(): @@ -236,8 +232,7 @@ def get_criteria_chat_history(): status_code = 500 return_request["message"] = str(e) - finally: - return jsonify(return_request), status_code + return jsonify(return_request), status_code @app.route("/getmentorchathistory", methods=['POST']) def get_mentor_chat_history(): @@ -279,8 +274,7 @@ def get_mentor_chat_history(): status_code = 500 return_request["message"] = str(e) - finally: - return jsonify(return_request), status_code + return jsonify(return_request), status_code @app.route("/setsystemcontext", methods=['POST']) def set_system_context(): @@ -323,8 +317,7 @@ def set_system_context(): status_code = 500 return_request["message"] = str(e) - finally: - return jsonify(return_request), status_code + return jsonify(return_request), status_code @app.route("/prompt", methods=['POST']) def prompt(): @@ -363,7 +356,7 @@ def prompt(): raise ValueError("Chat with given hash doesnt exsist") sys_context = chat_doc2.get("system_context", "") #RAG - top_docs = rm.get_relevant_documents_safe(data["user_message"]) + top_docs = rm.get_relevant_documents(data["user_message"]) formatted_docs = rm.format_docs(top_docs) rag_context_formated_docs = rm.format_docs_prompt(formatted_docs, prescript="Contex", count=1) @@ -371,30 +364,30 @@ def prompt(): paper_context = "" base_prompt = "" # Read all prompt components using UTF-8 to prevent decode issues - with open("helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt", "r", encoding="utf-8") as f: identify_kw_prompt = f.read() - with open("helpers/llm/prompts/searchstrings/userInputPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/searchstrings/userInputPrompt.txt", "r", encoding="utf-8") as f: user_input_prompt = f.read() - with open("helpers/llm/prompts/searchstrings/specificationFollowup.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/searchstrings/specificationFollowup.txt", "r", encoding="utf-8") as f: end_specification = f.read() user_input = f'User Input: {data["user_message"]} \n \n' if current_search_string.strip() == "": - with open("helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt", "r", encoding="utf-8") as f: base_prompt = f.read() paper_context = "" current_search_string_format = "General" else: - with open("helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt", "r", encoding="utf-8") as f: base_prompt = f.read() paper_context = f'Current search string: {current_search_string} \n \n' - with open("helpers/llm/prompts/conversion/3_formatContext.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/conversion/3_formatContext.txt", "r", encoding="utf-8") as f: user_input_context = f.read() - with open("helpers/llm/prompts/conversion/" + str(valid_ss_conversions.get(current_search_string_format, valid_ss_conversions["General"])), "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/conversion/" + str(valid_ss_conversions.get(current_search_string_format, valid_ss_conversions["General"])), "r", encoding="utf-8") as f: current_format = f.read() paper_context = paper_context + user_input_context + current_format @@ -477,8 +470,7 @@ def prompt(): status_code = 500 return_request["message"] = str(e) - finally: - return jsonify(return_request), status_code + return jsonify(return_request), status_code def check_missing_or_blank_fields(data, request_required_fields): @@ -567,21 +559,21 @@ def convert(): # ------------------------------------------------- try: with open( - "helpers/llm/prompts/conversion/1_BasePrompt.txt", + "app/helpers/llm/prompts/conversion/1_BasePrompt.txt", "r", encoding="utf-8" ) as f: base_prompt = f.read() with open( - "helpers/llm/prompts/conversion/2_userInputPrompt.txt", + "app/helpers/llm/prompts/conversion/2_userInputPrompt.txt", "r", encoding="utf-8" ) as f: user_input_prompt = f.read() with open( - "helpers/llm/prompts/conversion/3_formatContext.txt", + "app/helpers/llm/prompts/conversion/3_formatContext.txt", "r", encoding="utf-8" ) as f: @@ -589,14 +581,14 @@ def convert(): current_format_filename = valid_ss_conversions[current_search_string_format] with open( - "helpers/llm/prompts/conversion/" + str(current_format_filename), + "app/helpers/llm/prompts/conversion/" + str(current_format_filename), "r", encoding="utf-8" ) as f: current_format = f.read() with open( - "helpers/llm/prompts/conversion/5_conversionContext.txt", + "app/helpers/llm/prompts/conversion/5_conversionContext.txt", "r", encoding="utf-8" ) as f: @@ -604,14 +596,14 @@ def convert(): convert_to_format_filename = valid_ss_conversions[requested_conversion_format] with open( - "helpers/llm/prompts/conversion/" + str(convert_to_format_filename).strip(), + "app/helpers/llm/prompts/conversion/" + str(convert_to_format_filename).strip(), "r", encoding="utf-8" ) as f: convert_to_format = f.read() with open( - "helpers/llm/prompts/conversion/specificationFollowup.txt", + "app/helpers/llm/prompts/conversion/specificationFollowup.txt", "r", encoding="utf-8" ) as f: @@ -781,7 +773,7 @@ def criteria(): raise ValueError("Chat with given hash doesnt exsist") sys_context = chat_doc2.get("system_context", "") #RAG - top_docs = rm.get_relevant_documents_safe(data["user_message"]) + top_docs = rm.get_relevant_documents(data["user_message"]) formatted_docs = rm.format_docs(top_docs) rag_context_formated_docs = rm.format_docs_prompt(formatted_docs, prescript="Contex", count=1) @@ -789,20 +781,20 @@ def criteria(): paper_context = "" base_prompt = "" # Read all prompt components using UTF-8 to prevent decode issues - with open("helpers/llm/prompts/criteria/userInputPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/criteria/userInputPrompt.txt", "r", encoding="utf-8") as f: user_input_prompt = f.read() - with open("helpers/llm/prompts/criteria/specificationFollowup.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/criteria/specificationFollowup.txt", "r", encoding="utf-8") as f: end_specification = f.read() user_input = f'User Input: {data["user_message"]} \n \n' if current_criteria.strip() == "": - with open("helpers/llm/prompts/criteria/baseQuestionPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/criteria/baseQuestionPrompt.txt", "r", encoding="utf-8") as f: base_prompt = f.read() paper_context = "" else: - with open("helpers/llm/prompts/criteria/baseFollowupPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/criteria/baseFollowupPrompt.txt", "r", encoding="utf-8") as f: base_prompt = f.read() paper_context = f"Current inclusion/exclusion criteria:\n{current_criteria}\n\n" @@ -873,45 +865,38 @@ def criteria(): status_code = 500 return_request["message"] = str(e) - finally: - return jsonify(return_request), status_code + return jsonify(return_request), status_code @app.route("/ragquery", methods=["POST"]) def rag_query(): - request_required_fields = ["user_message"] - return_request = { - "status": False, - "message": "", - "user_message": "", - "top_documents": [] - } - status_code = 401 try: - data = request.json - if check_missing_or_blank_fields(data, request_required_fields): - raise ValueError("Request missing required fields") + data = request.get_json(silent=True) - user_message = data["user_message"] + if not isinstance(data, dict): + return jsonify({"error": "Invalid JSON body"}), 400 - # Safely retrieve documents - top_docs = rm.get_relevant_documents_safe(user_message) - formatted_docs_plaintext = rm.format_docs(top_docs) + query = data.get("query") - return_request.update({ - "status": True, - "message": "Successfully retrieved documents", - "user_message": user_message, - "top_documents": formatted_docs_plaintext - }) - status_code = 200 + if not isinstance(query, str) or not query.strip(): + return jsonify({"error": "Query is required"}), 400 - except Exception as e: - print(e) - return_request["message"] = str(e) - status_code = 500 + query = query.strip() + + # Native MongoDB retrieval (no LangChain). + documents = rm.get_relevant_documents(query) + + return jsonify({ + "query": query, + "total_results": len(documents), + "documents": documents + }), 200 + + except Exception: + app.logger.exception("RAG query failed") - finally: - return jsonify(return_request), status_code + return jsonify({ + "error": "RAG query failed" + }), 500 @app.route("/mentor", methods=["POST"]) def mentor(): @@ -964,8 +949,8 @@ def mentor(): raise ValueError("Chat with given hash doesnt exsist") sys_context = chat_doc2.get("system_context", "") - # Safely retrieve documents - top_docs = rm.get_relevant_documents_safe(user_message) + # Retrieve native dictionaries using MongoDB-hosted embeddings + top_docs = rm.get_relevant_documents(user_message) formatted_docs = rm.format_docs(top_docs) rag_context_formated_docs = rm.format_docs_prompt(formatted_docs, prescript="Contex", count=1) @@ -973,16 +958,16 @@ def mentor(): paper_context = "" base_prompt = "" # Read all prompt components using UTF-8 to prevent decode issues - with open("helpers/llm/prompts/mentor/userInputPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/mentor/userInputPrompt.txt", "r", encoding="utf-8") as f: user_input_prompt = f.read() - with open("helpers/llm/prompts/mentor/specificationFollowup.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/mentor/specificationFollowup.txt", "r", encoding="utf-8") as f: end_specification = f.read() user_input = f'User Input: {data["user_message"]} \n \n' - with open("helpers/llm/prompts/mentor/baseQuestionPrompt.txt", "r", encoding="utf-8") as f: + with open("app/helpers/llm/prompts/mentor/baseQuestionPrompt.txt", "r", encoding="utf-8") as f: base_prompt = f.read() rag_context = "The following is some relevant context and information in no perticular order to help you answer the users question: \n" + "\n".join(rag_context_formated_docs) + "\n\n" @@ -1045,5 +1030,4 @@ def mentor(): return_request["message"] = str(e) status_code = 500 - finally: - return jsonify(return_request), status_code \ No newline at end of file + return jsonify(return_request), status_code \ No newline at end of file diff --git a/backend/app/helpers/RAG/RetrieverManager.py b/backend/app/helpers/RAG/RetrieverManager.py new file mode 100644 index 0000000..fc9b8b0 --- /dev/null +++ b/backend/app/helpers/RAG/RetrieverManager.py @@ -0,0 +1,386 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pymongo import MongoClient + +if TYPE_CHECKING: + from langchain_core.documents import Document + + +class RetrieverManager: + """ + Manages document retrieval from MongoDB Atlas Vector Search + using MongoDB's hosted Automated Embedding functionality. + + Query embeddings are generated remotely by MongoDB Atlas. + No local embedding model is loaded or executed. + + Two retrieval interfaces are provided: + + 1. get_relevant_documents() + New retrieval method that returns native Python dictionaries. + Does not depend on LangChain for retrieval or formatting. + + 2. get_relevant_documents_safe() + Legacy retrieval method that returns LangChain Document objects. + Maintained for backward compatibility with existing RAG code. + + Both methods share the same MongoDB retrieval pipeline. + + The class also provides utilities for formatting retrieved + documents and managing the MongoDB connection. + """ + + def __init__( + self, + mongo_uri: str, + namespace: str, + index_name: str = "autoembed_index", + top_k: int = 5 + ): + """ + Initialize the MongoDB Atlas retriever. + + Args: + mongo_uri (str): + MongoDB Atlas connection string. + + namespace (str): + MongoDB database and collection in the format + "database.collection". + + index_name (str): + Name of the MongoDB Atlas Vector Search index. + Defaults to "autoembed_index". + + top_k (int): + Maximum number of relevant documents to retrieve. + Defaults to 5. + + Raises: + ValueError: + If top_k is invalid or namespace is incorrectly formatted. + + Notes: + Initializes the MongoDB client and selects the collection. + Does not load any embedding model locally. + + MongoClient manages connection pooling automatically. + The connection is established lazily when needed. + """ + + self.mongo_uri = mongo_uri + self.namespace = namespace + self.index_name = index_name + self.top_k = top_k + + if not isinstance(top_k, int) or isinstance(top_k, bool) or top_k < 1: + raise ValueError("top_k must be a positive integer.") + + if not isinstance(namespace, str) or "." not in namespace: + raise ValueError( + "namespace must have the format 'database.collection'." + ) + + database_name, collection_name = namespace.split(".", 1) + + if not database_name or not collection_name: + raise ValueError("Invalid MongoDB namespace.") + + # Initialize MongoDB client. + self.client = MongoClient( + self.mongo_uri, + serverSelectionTimeoutMS=10000 + ) + + # Select the target database and collection. + self.collection = self.client[database_name][collection_name] + + # ============================================================ + # New Retrieval Method (Native MongoDB) + # ============================================================ + + def get_relevant_documents(self, query: str) -> list[dict]: + """ + Retrieve relevant documents directly from MongoDB Atlas. + + This is the NEW retrieval method and should be preferred + for new implementations. + + Uses MongoDB Atlas Automated Embedding to convert the + query into a vector remotely and retrieve semantically + similar document chunks. + + Unlike the legacy method, this method returns native + Python dictionaries and does not use LangChain. + + Args: + query (str): + Natural-language search query. + + Returns: + list[dict]: + List of MongoDB documents containing: + + - content: Retrieved document text. + - paper_id: Associated paper identifier. + - source: Original document source. + - page: Page number. + - chunk_index: Position of the text chunk. + - score: MongoDB vector similarity score. + + Only fields present in the collection are returned. + + Raises: + ValueError: + If the query is empty or invalid. + + pymongo.errors.PyMongoError: + If the MongoDB query fails. + + Notes: + - No local embedding generation occurs. + - MongoDB Atlas handles query embedding remotely. + - Results are limited by self.top_k. + - The Vector Search index must support autoEmbed. + - The indexed text field must be "content". + """ + + if not isinstance(query, str) or not query.strip(): + raise ValueError("Query must be a non-empty string.") + + pipeline = [ + { + "$vectorSearch": { + "index": self.index_name, + "path": "content", + "query": { + "text": query.strip() + }, + "numCandidates": min( + max(self.top_k * 10, 50), + 10000 + ), + "limit": self.top_k + } + }, + { + "$project": { + "_id": 0, + "paper_id": 1, + "source": 1, + "page": 1, + "chunk_index": 1, + "content": 1, + "score": { + "$meta": "vectorSearchScore" + } + } + } + ] + + return list(self.collection.aggregate(pipeline)) + + # ============================================================ + # Legacy Retrieval Method (LangChain Compatibility) + # ============================================================ + + def get_relevant_documents_safe(self, query: str) -> list[Document]: + """ + Retrieve relevant documents as LangChain Document objects. + + LEGACY METHOD: + Maintained for backward compatibility with code that + expects LangChain Document objects. + + Internally calls the new get_relevant_documents() method + and converts the returned MongoDB dictionaries into + LangChain Document objects. + + This method does not use LangChain for vector retrieval. + LangChain is only used for the Document data structure. + + Args: + query (str): + Natural-language search query. + + Returns: + list[Document]: + List of LangChain Document objects containing: + + - page_content: Retrieved text. + - metadata: Source information, page number, + chunk index, paper ID, and similarity score. + + Raises: + ValueError: + If the query is empty or invalid. + + pymongo.errors.PyMongoError: + If MongoDB retrieval fails. + + Notes: + - No local embedding model is loaded. + - Retrieval is performed using MongoDB Atlas. + - Conversion adds LangChain compatibility overhead. + - Prefer get_relevant_documents() for new code. + """ + + # Only legacy callers need the optional LangChain Document type. + from langchain_core.documents import Document + + results = self.get_relevant_documents(query) + + documents = [] + + for result in results: + content = result.get("content", "") + + if not content: + continue + + metadata = { + key: value + for key, value in result.items() + if key != "content" + } + + documents.append( + Document( + page_content=content, + metadata=metadata + ) + ) + + return documents + + # ============================================================ + # Document Formatting Helpers + # ============================================================ + + @staticmethod + def format_docs(docs: list) -> list[str]: + """ + Extract text content from retrieved documents. + + Supports both retrieval formats: + + 1. Native MongoDB dictionaries returned by + get_relevant_documents(). + + 2. LangChain Document objects returned by + get_relevant_documents_safe(). + + Args: + docs (list): + List of MongoDB dictionaries or LangChain Documents. + + Returns: + list[str]: + List containing only the text content of each document. + + Notes: + - Empty document content is ignored. + - Document metadata is not included in the output. + - Useful for preparing context for LLM prompts. + """ + + texts = [] + + for doc in docs: + if isinstance(doc, dict): + text = ( + doc.get("content") + or doc.get("text") + or doc.get("page_content") + ) + else: + text = ( + getattr(doc, "page_content", None) + or getattr(doc, "text", None) + ) + + if text: + texts.append(text) + + return texts + + @staticmethod + def format_docs_prompt( + docs: list[str], + prescript: str = "", + count: int = 0 + ) -> list[str]: + """ + Add a prefix before each document's text. + + Useful when preparing retrieved content for inclusion + in an LLM prompt. + + Args: + docs (list[str]): + List of document text strings. + + prescript (str): + Prefix to place before each document. + Defaults to an empty string. + + count (int): + If 1, appends a sequential number to each prefix. + Otherwise, uses the same prefix for all documents. + + Returns: + list[str]: + List of formatted text strings. + + Example: + Input: + docs = ["First text", "Second text"] + prescript = "Document" + count = 1 + + Output: + [ + "Document 1: First text", + "Document 2: Second text" + ] + + Notes: + If prescript is empty, the original list is returned + unchanged. + """ + + if prescript == "": + return docs + + texts = [] + + for i, d in enumerate(docs): + if count == 1: + pres = f"{prescript} {i + 1}: " + else: + pres = f"{prescript}: " + + texts.append(pres + d) + + return texts + + # ============================================================ + # Connection Management + # ============================================================ + + def close(self) -> None: + """ + Close the MongoDB client and its connection pool. + + Should be called when the RetrieverManager is no longer + needed, typically during application shutdown. + + Notes: + - Releases MongoDB connection resources. + - Does not delete documents or modify the database. + - A closed MongoClient should not be reused. + """ + + self.client.close() diff --git a/backend/helpers/cryptographic_helpers.py b/backend/app/helpers/cryptographic_helpers.py similarity index 100% rename from backend/helpers/cryptographic_helpers.py rename to backend/app/helpers/cryptographic_helpers.py diff --git a/backend/helpers/db/mongodbhelpers.py b/backend/app/helpers/db/mongodbhelpers.py similarity index 100% rename from backend/helpers/db/mongodbhelpers.py rename to backend/app/helpers/db/mongodbhelpers.py diff --git a/backend/helpers/lenzapi.py b/backend/app/helpers/lenzapi.py similarity index 100% rename from backend/helpers/lenzapi.py rename to backend/app/helpers/lenzapi.py diff --git a/backend/helpers/llm/llmPromptingUtils.py b/backend/app/helpers/llm/llmPromptingUtils.py similarity index 98% rename from backend/helpers/llm/llmPromptingUtils.py rename to backend/app/helpers/llm/llmPromptingUtils.py index 7b5c0d8..173b7b1 100644 --- a/backend/helpers/llm/llmPromptingUtils.py +++ b/backend/app/helpers/llm/llmPromptingUtils.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, ValidationError from openai import OpenAI from google import genai -from helpers.llm.llmReturnFormat import Response, ResponseCriteria, ResponseMentor +from app.helpers.llm.llmReturnFormat import Response, ResponseCriteria, ResponseMentor import json diff --git a/backend/helpers/llm/llmReturnFormat.py b/backend/app/helpers/llm/llmReturnFormat.py similarity index 100% rename from backend/helpers/llm/llmReturnFormat.py rename to backend/app/helpers/llm/llmReturnFormat.py diff --git a/backend/helpers/llm/promptBuilder.py b/backend/app/helpers/llm/promptBuilder.py similarity index 100% rename from backend/helpers/llm/promptBuilder.py rename to backend/app/helpers/llm/promptBuilder.py diff --git a/backend/helpers/llm/prompts/conversion/1_BasePrompt.txt b/backend/app/helpers/llm/prompts/conversion/1_BasePrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/conversion/1_BasePrompt.txt rename to backend/app/helpers/llm/prompts/conversion/1_BasePrompt.txt diff --git a/backend/helpers/llm/prompts/conversion/2_userInputPrompt.txt b/backend/app/helpers/llm/prompts/conversion/2_userInputPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/conversion/2_userInputPrompt.txt rename to backend/app/helpers/llm/prompts/conversion/2_userInputPrompt.txt diff --git a/backend/helpers/llm/prompts/conversion/3_formatContext.txt b/backend/app/helpers/llm/prompts/conversion/3_formatContext.txt similarity index 100% rename from backend/helpers/llm/prompts/conversion/3_formatContext.txt rename to backend/app/helpers/llm/prompts/conversion/3_formatContext.txt diff --git a/backend/helpers/llm/prompts/conversion/4_generalformat.txt b/backend/app/helpers/llm/prompts/conversion/4_generalformat.txt similarity index 100% rename from backend/helpers/llm/prompts/conversion/4_generalformat.txt rename to backend/app/helpers/llm/prompts/conversion/4_generalformat.txt diff --git a/backend/helpers/llm/prompts/conversion/4_googlescholarformat.txt b/backend/app/helpers/llm/prompts/conversion/4_googlescholarformat.txt similarity index 100% rename from backend/helpers/llm/prompts/conversion/4_googlescholarformat.txt rename to backend/app/helpers/llm/prompts/conversion/4_googlescholarformat.txt diff --git a/backend/helpers/llm/prompts/conversion/4_ieeeXploreformat.txt b/backend/app/helpers/llm/prompts/conversion/4_ieeeXploreformat.txt similarity index 100% rename from backend/helpers/llm/prompts/conversion/4_ieeeXploreformat.txt rename to backend/app/helpers/llm/prompts/conversion/4_ieeeXploreformat.txt diff --git a/backend/helpers/llm/prompts/conversion/5_conversionContext.txt b/backend/app/helpers/llm/prompts/conversion/5_conversionContext.txt similarity index 100% rename from backend/helpers/llm/prompts/conversion/5_conversionContext.txt rename to backend/app/helpers/llm/prompts/conversion/5_conversionContext.txt diff --git a/backend/helpers/llm/prompts/conversion/specificationFollowup.txt b/backend/app/helpers/llm/prompts/conversion/specificationFollowup.txt similarity index 100% rename from backend/helpers/llm/prompts/conversion/specificationFollowup.txt rename to backend/app/helpers/llm/prompts/conversion/specificationFollowup.txt diff --git a/backend/helpers/llm/prompts/criteria/baseFollowupPrompt.txt b/backend/app/helpers/llm/prompts/criteria/baseFollowupPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/criteria/baseFollowupPrompt.txt rename to backend/app/helpers/llm/prompts/criteria/baseFollowupPrompt.txt diff --git a/backend/helpers/llm/prompts/criteria/baseQuestionPrompt.txt b/backend/app/helpers/llm/prompts/criteria/baseQuestionPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/criteria/baseQuestionPrompt.txt rename to backend/app/helpers/llm/prompts/criteria/baseQuestionPrompt.txt diff --git a/backend/helpers/llm/prompts/criteria/specificationFollowup.txt b/backend/app/helpers/llm/prompts/criteria/specificationFollowup.txt similarity index 100% rename from backend/helpers/llm/prompts/criteria/specificationFollowup.txt rename to backend/app/helpers/llm/prompts/criteria/specificationFollowup.txt diff --git a/backend/helpers/llm/prompts/criteria/userInputPrompt.txt b/backend/app/helpers/llm/prompts/criteria/userInputPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/criteria/userInputPrompt.txt rename to backend/app/helpers/llm/prompts/criteria/userInputPrompt.txt diff --git a/backend/helpers/llm/prompts/mentor/baseQuestionPrompt.txt b/backend/app/helpers/llm/prompts/mentor/baseQuestionPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/mentor/baseQuestionPrompt.txt rename to backend/app/helpers/llm/prompts/mentor/baseQuestionPrompt.txt diff --git a/backend/helpers/llm/prompts/mentor/specificationFollowup.txt b/backend/app/helpers/llm/prompts/mentor/specificationFollowup.txt similarity index 100% rename from backend/helpers/llm/prompts/mentor/specificationFollowup.txt rename to backend/app/helpers/llm/prompts/mentor/specificationFollowup.txt diff --git a/backend/helpers/llm/prompts/mentor/userInputPrompt.txt b/backend/app/helpers/llm/prompts/mentor/userInputPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/mentor/userInputPrompt.txt rename to backend/app/helpers/llm/prompts/mentor/userInputPrompt.txt diff --git a/backend/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt b/backend/app/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt rename to backend/app/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt diff --git a/backend/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt b/backend/app/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt rename to backend/app/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt diff --git a/backend/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt b/backend/app/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt rename to backend/app/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt diff --git a/backend/helpers/llm/prompts/searchstrings/specificationFollowup.txt b/backend/app/helpers/llm/prompts/searchstrings/specificationFollowup.txt similarity index 100% rename from backend/helpers/llm/prompts/searchstrings/specificationFollowup.txt rename to backend/app/helpers/llm/prompts/searchstrings/specificationFollowup.txt diff --git a/backend/helpers/llm/prompts/searchstrings/userInputPrompt.txt b/backend/app/helpers/llm/prompts/searchstrings/userInputPrompt.txt similarity index 100% rename from backend/helpers/llm/prompts/searchstrings/userInputPrompt.txt rename to backend/app/helpers/llm/prompts/searchstrings/userInputPrompt.txt diff --git a/backend/helpers/scholarlyapi.py b/backend/app/helpers/scholarlyapi.py similarity index 100% rename from backend/helpers/scholarlyapi.py rename to backend/app/helpers/scholarlyapi.py diff --git a/backend/helpers/xploreapi/xploreapi.py b/backend/app/helpers/xploreapi/xploreapi.py similarity index 100% rename from backend/helpers/xploreapi/xploreapi.py rename to backend/app/helpers/xploreapi/xploreapi.py diff --git a/backend/environment.yml b/backend/environment.yml deleted file mode 100644 index 5682fa9..0000000 --- a/backend/environment.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: seallapp -channels: - - defaults -dependencies: - - python=3.10 - - pip - - setuptools - - wheel - - ca-certificates - - openssl - - zlib - - sqlite - - tk - - libffi - - bzip2 - - xz - - pip: - - aiohappyeyeballs==2.6.1 - - aiohttp==3.12.13 - - aiosignal==1.4.0 - - alabaster==1.0.0 - - annotated-types==0.7.0 - - anyio==4.9.0 - - arrow==1.3.0 - - async-timeout==5.0.1 - - attrs==25.3.0 - - babel==2.17.0 - - bcrypt==4.3.0 - - beautifulsoup4==4.13.4 - - bibtexparser==1.4.3 - - blinker==1.9.0 - - cachetools==5.5.2 - - certifi==2025.4.26 - - cffi==1.17.1 - - charset-normalizer==3.4.2 - - click==8.2.1 - - colorama==0.4.6 - - deprecated==1.2.18 - - distro==1.9.0 - - dnspython==2.7.0 - - docutils==0.21.2 - - dotenv==0.9.9 - - et-xmlfile==2.0.0 - - exceptiongroup==1.3.0 - - fake-useragent==2.2.0 - - flask==3.1.1 - - flask-cors==6.0.0 - - flask-pymongo==3.0.1 - - free-proxy==1.1.3 - - frozenlist==1.7.0 - - google==3.0.0 - - google-ai-generativelanguage==0.6.15 - - google-api-core==2.25.1rc0 - - google-api-python-client==2.172.0 - - google-auth==2.40.3 - - google-auth-httplib2==0.2.0 - - google-genai==1.19.0 - - google-generativeai==0.8.5 - - googleapis-common-protos==1.70.0 - - grpcio==1.73.0 - - grpcio-status==1.71.0 - - gunicorn==23.0.0 - - h11==0.16.0 - - httpcore==1.0.9 - - httplib2==0.22.0 - - httpx==0.28.1 - - idna==3.10 - - imagesize==1.4.1 - - itsdangerous==2.2.0 - - jinja2==3.1.6 - - jiter==0.10.0 - - lxml==6.0.0 - - markupsafe==3.0.2 - - multidict==6.6.3 - - numpy==2.2.6 - - openai==1.93.0 - - openpyxl==3.1.5 - - outcome==1.3.0.post0 - - packaging==25.0 - - pandas==2.3.0 - - propcache==0.3.2 - - proto-plus==1.26.1 - - protobuf==5.29.5 - - pyasn1==0.6.1 - - pyasn1-modules==0.4.2 - - pycparser==2.22 - - pycurl==7.45.6 - - pydantic==2.11.5 - - pydantic-core==2.33.2 - - pygments==2.19.2 - - pymongo==4.13.0 - - pyparsing==3.2.3 - - pysocks==1.7.1 - - python-dateutil==2.9.0.post0 - - python-dotenv==1.1.0 - - pytz==2025.2 - - requests==2.32.4 - - rsa==4.9.1 - - scholarly==1.7.11 - - selenium==4.34.0 - - six==1.17.0 - - sniffio==1.3.1 - - snowballstemmer==3.0.1 - - sortedcontainers==2.4.0 - - soupsieve==2.7 - - sphinx==8.1.3 - - sphinx-rtd-theme==3.0.2 - - sphinxcontrib-applehelp==2.0.0 - - sphinxcontrib-devhelp==2.0.0 - - sphinxcontrib-htmlhelp==2.1.0 - - sphinxcontrib-jquery==4.1 - - sphinxcontrib-jsmath==1.0.1 - - sphinxcontrib-qthelp==2.0.0 - - sphinxcontrib-serializinghtml==2.0.0 - - tomli==2.2.1 - - tqdm==4.67.1 - - trio==0.30.0 - - trio-websocket==0.12.2 - - types-python-dateutil==2.9.0.20250516 - - typing-extensions==4.14.0 - - typing-inspection==0.4.1 - - tzdata==2025.2 - - uritemplate==4.2.0 - - urllib3==2.4.0 - - websocket-client==1.8.0 - - websockets==15.0.1 - - werkzeug==3.1.3 - - wrapt==1.17.2 - - wsproto==1.2.0 - - yarl==1.20.1 diff --git a/backend/helpers/RAG/RetrieverManager.py b/backend/helpers/RAG/RetrieverManager.py deleted file mode 100644 index 8c4bdb3..0000000 --- a/backend/helpers/RAG/RetrieverManager.py +++ /dev/null @@ -1,103 +0,0 @@ -from langchain_community.embeddings import HuggingFaceEmbeddings -from langchain_community.vectorstores import MongoDBAtlasVectorSearch -import os - -class RetrieverManager: - """ - Manages initialization and use of a MongoDB Atlas vector retriever, - along with helper formatting utilities for retrieved documents. - """ - - def __init__( - self, - mongo_uri: str, - namespace: str = "SLRMentor.document_rag", - index_name: str = "vector_index", - top_k: int = 5 - ): - """ - Initialize the retriever. - """ - self.mongo_uri = mongo_uri - self.namespace = namespace - self.index_name = index_name - self.top_k = top_k - - # Initialize embedding model - self.embedding_model = HuggingFaceEmbeddings( - model_name="sentence-transformers/all-MiniLM-L6-v2" - ) - - # Initialize vector store - self.vector_store = MongoDBAtlasVectorSearch.from_connection_string( - connection_string=self.mongo_uri, - namespace=self.namespace, - embedding=self.embedding_model, - index_name=self.index_name - ) - - # Initialize retriever - self.retriever = self.vector_store.as_retriever( - search_kwargs={"k": self.top_k} - ) - - # --------------------------- - # Retriever Access Helpers - # --------------------------- - def get_relevant_documents_safe(self, query: str): - """ - Safely call whichever method the retriever provides. - """ - if hasattr(self.retriever, "get_relevant_documents"): - return self.retriever.get_relevant_documents(query) - elif hasattr(self.retriever, "retrieve"): - return self.retriever.retrieve(query) - elif hasattr(self.retriever, "invoke"): - return self.retriever.invoke(query) - else: - raise AttributeError("Retriever does not support any known query method.") - - # --------------------------- - # Document Formatting Helpers - # --------------------------- - @staticmethod - def format_docs(docs: list) -> list: - """ - Extract text from list of documents (dicts or LangChain Document objects). - """ - texts = [] - - for doc in docs: - if isinstance(doc, dict): # MongoDB dict - text = doc.get("text") or doc.get("page_content") - else: # LangChain Document - text = ( - getattr(doc, "page_content", None) - or getattr(doc, "text", None) - ) - if text: - texts.append(text) - - return texts - - @staticmethod - def format_docs_prompt( - docs: list, - prescript: str = "", - count: int = 0 - ) -> list: - """ - Same as format_docs but also injects a prescript before each item. - """ - if prescript == "": - return docs - - texts = [] - for i, d in enumerate(docs): - if count == 1: - pres = f"{prescript} {i+1}: " - else: - pres = f"{prescript}: " - texts.append(pres + d) - - return texts diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..1e8cfa8 --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "SLRmentor_Backend" +version = "0.1.0" +description = "SLRmentor backend is the api server that servers the frontends requests" +readme = "README.md" +requires-python = ">=3.14.5" +dependencies = [ + "flask>=3.1.3", + "flask-cors>=6.0.5", + "flask-pymongo>=2.3.0", + "google-genai>=2.10.0", + "gunicorn>=26.1.0", + "openai>=2.44.0", + "pydantic>=2.13.4", + "pymongo[srv]>=4.6.1", + "python-dotenv>=1.2.4", + "requests>=2.34.2", +] diff --git a/backend/requirements.txt b/backend/requirements.txt deleted file mode 100644 index f72a736..0000000 --- a/backend/requirements.txt +++ /dev/null @@ -1,167 +0,0 @@ -# Heavy / core ML and data packages first -torch==2.9.0 -transformers==4.57.1 -sentence-transformers==5.1.2 -#numpy==2.2.6 -scipy==1.15.3 -#pandas==2.3.0 -scikit-learn==1.7.2 - -# Remaining packages (alphabetical-ish, non-GPU) -aiohappyeyeballs==2.6.1 -aiohttp==3.12.13 -aiosignal==1.4.0 -alabaster==1.0.0 -annotated-types==0.7.0 -anyio==4.9.0 -arrow==1.3.0 -async-timeout==4.0.3 -attrs==25.3.0 -babel==2.17.0 -bcrypt==4.3.0 -beautifulsoup4==4.13.4 -bibtexparser==1.4.3 -blinker==1.9.0 -cachetools==5.5.2 -certifi==2025.4.26 -cffi==1.17.1 -charset-normalizer==3.4.2 -click==8.2.1 -colorama==0.4.6 -dataclasses-json==0.6.7 -Deprecated==1.2.18 -distro==1.9.0 -dnspython==2.7.0 -docutils==0.21.2 -dotenv==0.9.9 -et_xmlfile==2.0.0 -exceptiongroup==1.3.0 -fake-useragent==2.2.0 -filelock==3.20.0 -Flask==3.1.1 -flask-cors==6.0.0 -Flask-PyMongo==3.0.1 -free_proxy==1.1.3 -frozenlist==1.7.0 -fsspec==2025.9.0 -google==3.0.0 -google-ai-generativelanguage==0.6.15 -google-api-core==2.25.1rc0 -google-api-python-client==2.172.0 -google-auth==2.40.3 -google-auth-httplib2==0.2.0 -google-genai==1.19.0 -google-generativeai==0.8.5 -googleapis-common-protos==1.70.0 -#gpt4all==2.8.2 -greenlet==3.2.4 -grpcio==1.73.0 -grpcio-status==1.71.0 -gunicorn==23.0.0 -h11==0.16.0 -httpcore==1.0.9 -httplib2==0.22.0 -httpx==0.28.1 -httpx-sse==0.4.3 -huggingface-hub==0.36.0 -idna==3.10 -imagesize==1.4.1 -itsdangerous==2.2.0 -Jinja2==3.1.6 -jiter==0.10.0 -joblib==1.5.2 -jsonpatch==1.33 -jsonpointer==3.0.0 -langchain==1.0.2 -langchain-classic==1.0.0 -langchain-community==0.4 -langchain-core==1.0.1 -langchain-huggingface==1.0.0 -langchain-mongodb==0.7.1 -langchain-text-splitters==1.0.0 -langchainhub==0.1.21 -langgraph==1.0.1 -langgraph-checkpoint==3.0.0 -langgraph-prebuilt==1.0.1 -langgraph-sdk==0.2.9 -langsmith==0.4.38 -lark==1.3.0 -lxml==6.0.0 -MarkupSafe==3.0.2 -marshmallow==3.26.1 -mpmath==1.3.0 -multidict==6.6.3 -mypy_extensions==1.1.0 -networkx==3.4.2 -openai==1.93.0 -openpyxl==3.1.5 -orjson==3.11.4 -ormsgpack==1.11.0 -outcome==1.3.0.post0 -packaging==24.2 -pillow==12.0.0 -propcache==0.3.2 -proto-plus==1.26.1 -protobuf==5.29.5 -pyasn1==0.6.1 -pyasn1_modules==0.4.2 -pycparser==2.22 -pycurl==7.45.6 -pydantic==2.11.5 -pydantic-settings==2.11.0 -pydantic_core==2.33.2 -Pygments==2.19.2 -pymongo==4.15.3 -pyparsing==3.2.3 -pypdf==6.1.3 -PySocks==1.7.1 -python-dateutil==2.9.0.post0 -python-dotenv==1.1.0 -pytz==2025.2 -PyYAML==6.0.3 -regex==2025.10.23 -requests==2.32.5 -requests-toolbelt==1.0.0 -rsa==4.9.1 -safetensors==0.6.2 -scholarly==1.7.11 -selenium==4.34.0 -six==1.17.0 -sniffio==1.3.1 -snowballstemmer==3.0.1 -sortedcontainers==2.4.0 -soupsieve==2.7 -Sphinx==8.1.3 -sphinx-rtd-theme==3.0.2 -sphinxcontrib-applehelp==2.0.0 -sphinxcontrib-devhelp==2.0.0 -sphinxcontrib-htmlhelp==2.1.0 -sphinxcontrib-jquery==4.1 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==2.0.0 -sphinxcontrib-serializinghtml==2.0.0 -SQLAlchemy==2.0.44 -sympy==1.14.0 -tenacity==9.1.2 -threadpoolctl==3.6.0 -tokenizers==0.22.1 -tomli==2.2.1 -tqdm==4.67.1 -trio==0.30.0 -trio-websocket==0.12.2 -types-python-dateutil==2.9.0.20250516 -types-requests==2.32.4.20250913 -typing-inspect==0.9.0 -typing-inspection==0.4.1 -typing_extensions==4.14.0 -tzdata==2025.2 -uritemplate==4.2.0 -urllib3==2.4.0 -websocket-client==1.8.0 -websockets==15.0.1 -Werkzeug==3.1.3 -wrapt==1.17.2 -wsproto==1.2.0 -xxhash==3.6.0 -yarl==1.20.1 -zstandard==0.25.0 diff --git a/backend/uv.lock b/backend/uv.lock new file mode 100644 index 0000000..830daad --- /dev/null +++ b/backend/uv.lock @@ -0,0 +1,975 @@ +version = 1 +revision = 3 +requires-python = ">=3.14.5" + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/1c/f41d4e74c28ab327ff3acd36053f7ea506c55872d7a90b0fa71aa3ab0c89/charset_normalizer-3.5.2.tar.gz", hash = "sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef", size = 172659, upload-time = "2026-09-30T04:39:23.398Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/ca/7aa91362a2f77ac8e9e28a9b902a74f7d0e11a851ef0d27a74308da8cd90/charset_normalizer-3.5.2-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187", size = 226010, upload-time = "2026-09-30T04:36:27.669Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cf/ac8878d0322cf88a1aad4c7b147db32ca0bd806eb0060957b2e31486dbe6/charset_normalizer-3.5.2-cp314-cp314-android_24_x86_64.whl", hash = "sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad", size = 239266, upload-time = "2026-09-30T04:36:29.434Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6d/9a08d7e0b29b7208e2c6c01dc56c8e0520e7c7beadbbfb024b58fd69c8a5/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf", size = 206783, upload-time = "2026-09-30T04:36:30.872Z" }, + { url = "https://files.pythonhosted.org/packages/82/44/b0aa350280e6ff5a5492d17cf10460dd39d5ee848f872f7ba2df10607f60/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995", size = 210449, upload-time = "2026-09-30T04:36:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/40db9aa9f5907bb0e6f8b6d64064bf8852fb33d4b813ff9414911df7647c/charset_normalizer-3.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424", size = 367815, upload-time = "2026-09-30T04:36:34.197Z" }, + { url = "https://files.pythonhosted.org/packages/7f/72/9c5e7707b57c8ddfa9ddf7b0b1d009d7fbab9e9e887d5b721060f37e307d/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13", size = 247095, upload-time = "2026-09-30T04:36:35.803Z" }, + { url = "https://files.pythonhosted.org/packages/83/09/71e453691e927de4ddf792770cfaab3f49d494e222f66ea5e404bbd5e39c/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d", size = 233942, upload-time = "2026-09-30T04:36:37.407Z" }, + { url = "https://files.pythonhosted.org/packages/9f/86/85c84e4da8b27dd409577d9437926ff581c5f9d3c66038dc68c1a526de51/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4", size = 273762, upload-time = "2026-09-30T04:36:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/564955a4b5f2ccb410ab480bbe8c6a18063ff27f2d35458731c4a5335df9/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438", size = 269514, upload-time = "2026-09-30T04:36:40.469Z" }, + { url = "https://files.pythonhosted.org/packages/18/24/bad3ac4271589df29cf5ce2f5ae490518a5739358052bd0d61209e6fea54/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a", size = 255056, upload-time = "2026-09-30T04:36:42.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3e/350d89ad49916b86554d6f5f2d03ec1152148f87e5ff735106c6a03b1a36/charset_normalizer-3.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56", size = 251311, upload-time = "2026-09-30T04:36:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/56/5b/4970a2d154df502e133402906dd04e3ae7cada7b3011283c88d0479a2585/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd", size = 250190, upload-time = "2026-09-30T04:36:45.185Z" }, + { url = "https://files.pythonhosted.org/packages/88/8c/f1a91bddc8fb47c2889e29ea7ea49a194eb0d9868675d786806519c00d76/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204", size = 237098, upload-time = "2026-09-30T04:36:46.689Z" }, + { url = "https://files.pythonhosted.org/packages/24/0e/bb5dace3cc7e79068425386a6589c19b5a2ab5fefc2a46abea6919683332/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7", size = 274228, upload-time = "2026-09-30T04:36:48.31Z" }, + { url = "https://files.pythonhosted.org/packages/9d/79/b849ad523017ea9f5a45581bbebed91439e0cf42fd2860a6f64e358eb5a6/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd", size = 252401, upload-time = "2026-09-30T04:36:50.091Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/75469d690cf47200bce8f6cad7655724fc23148e147abfc5ce78b5f65863/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc", size = 271846, upload-time = "2026-09-30T04:36:51.719Z" }, + { url = "https://files.pythonhosted.org/packages/26/cd/6d52d3c7437cdcf2e310ce9f28f282e733d4ef60ed19105d1819c356255f/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874", size = 258151, upload-time = "2026-09-30T04:36:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4c/070b38bdb5f49a70199fce923ec0726a49536a63ab262abbfcaaf351110b/charset_normalizer-3.5.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655", size = 143570, upload-time = "2026-09-30T04:36:54.816Z" }, + { url = "https://files.pythonhosted.org/packages/81/84/9ebfc8ed6c8c4fcd8e726ff6bf220cc8deb3966e31dce9be8dd8aa017e64/charset_normalizer-3.5.2-cp314-cp314-win32.whl", hash = "sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0", size = 186412, upload-time = "2026-09-30T04:36:56.643Z" }, + { url = "https://files.pythonhosted.org/packages/d1/78/5ed86f743d4bc350db307e7636419a0a5ee1d91806d30c7f667bd5c80dae/charset_normalizer-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c", size = 210965, upload-time = "2026-09-30T04:36:58.205Z" }, + { url = "https://files.pythonhosted.org/packages/53/94/a3a7698e9b1a395e1eb99ccd9a324be9347973bff4e72db2a06496d7cd27/charset_normalizer-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253", size = 200406, upload-time = "2026-09-30T04:36:59.764Z" }, + { url = "https://files.pythonhosted.org/packages/c1/48/c5dd00d5ef7791f02666de250a5bb6071e29b7e133cf4b835800b6d3bc27/charset_normalizer-3.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709", size = 396871, upload-time = "2026-09-30T04:37:01.543Z" }, + { url = "https://files.pythonhosted.org/packages/12/c8/8379554b42e8368161d898476686947a0fdbd3e8865170d7909dcabfdee8/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084", size = 249711, upload-time = "2026-09-30T04:37:03.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/eb/2ddb1035d17320caa9f41682935123a9a250277b261c3efc86b2d2a21343/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb", size = 231146, upload-time = "2026-09-30T04:37:04.721Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/2ecb4bde104322cd7859d6594fcfa74649f8d90b3221c9feecbef149875b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f", size = 271552, upload-time = "2026-09-30T04:37:06.295Z" }, + { url = "https://files.pythonhosted.org/packages/3f/98/9d5f6ebc3aee9fef5d30b4aff11fb2ab7a1222b4064f8ef2c7c87cde217a/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09", size = 269810, upload-time = "2026-09-30T04:37:07.905Z" }, + { url = "https://files.pythonhosted.org/packages/09/e1/a3b06a10461b1b7628853c934c644e03bc28e42767116afb52f19a56519b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80", size = 259845, upload-time = "2026-09-30T04:37:09.554Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/6f561f74a296cf27d61775a1dc665ad13f3bff6a798810ca05907f37a7c4/charset_normalizer-3.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c", size = 252825, upload-time = "2026-09-30T04:37:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/69e13ca3b18f43e0eafcd34c04a45b732ae22a43b54a5fc9e119103356eb/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f", size = 252659, upload-time = "2026-09-30T04:37:12.941Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/ace29806a0dae18939919c76ba526472d83214afa101105fabff2cf30625/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03", size = 238819, upload-time = "2026-09-30T04:37:14.659Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c1/6116d52a2e3311ec80f21f5fb5e17b27405f10b9608af8f6e69516841a1b/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604", size = 272973, upload-time = "2026-09-30T04:37:16.346Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/9955c7e93bba10a9c7e8f7a5031b7ced66f3a1883a55c00712b8d5850ff3/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8", size = 253692, upload-time = "2026-09-30T04:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/bb/33/2a6ae7fdc1b10cb581cef91addd8cdfc5f40d50abb5702309369d5834579/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93", size = 268757, upload-time = "2026-09-30T04:37:19.877Z" }, + { url = "https://files.pythonhosted.org/packages/a2/22/80992720a0282cd39bba1db35868e6b9c22f41281160143a836544bc1d8a/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915", size = 262220, upload-time = "2026-09-30T04:37:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/92/9f/181fd07e1bffea1d95cd80c84ac537354f50699c22cfc4d3c02b6fc16208/charset_normalizer-3.5.2-cp314-cp314t-win32.whl", hash = "sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5", size = 197797, upload-time = "2026-09-30T04:37:23.235Z" }, + { url = "https://files.pythonhosted.org/packages/49/1c/25d8415ec1c4f2f41f1680435e4c87cfb378ff2f677d950946f2a45d0632/charset_normalizer-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc", size = 222765, upload-time = "2026-09-30T04:37:24.891Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b4/46b48f013dadfc0d0d33b375438e31bdf5a989dc68389c6bf627054d4df9/charset_normalizer-3.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105", size = 210985, upload-time = "2026-09-30T04:37:26.634Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e9/34e597dee616d0b8ee4b34d29399e85c2204ade174157a48505d42baa4ff/charset_normalizer-3.5.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26", size = 367676, upload-time = "2026-09-30T04:37:28.329Z" }, + { url = "https://files.pythonhosted.org/packages/60/9f/a5d1c91c0263745e2cd344c5a4415d787c575501ab1d449f1148ac6b495d/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364", size = 248414, upload-time = "2026-09-30T04:37:30.167Z" }, + { url = "https://files.pythonhosted.org/packages/26/79/e697f77464748a3ee3cf490c83d592459400d4898380d66c38366b03080c/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253", size = 244775, upload-time = "2026-09-30T04:37:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/3d42a42e18ea066e2513936fd678a00696e77878b5ae04528976abdbcb83/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0", size = 273877, upload-time = "2026-09-30T04:37:33.661Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/8a28136f3938ba9836f84280ce0c4d61ed1cf15a036b2034900c62634162/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc", size = 269455, upload-time = "2026-09-30T04:37:35.573Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a1/4fbf5d0f0f1b2a080474c1cf9a2f12c4c6531bb0e8ba591055e846d2b4e9/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229", size = 257336, upload-time = "2026-09-30T04:37:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a2/8b50aa320adb880ad579518e6f718f24944804b42a88b83d267d5d444125/charset_normalizer-3.5.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5", size = 257584, upload-time = "2026-09-30T04:37:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/a5/57/50e3fed84e175f40349bd0da7a4fce94c87f0378f52d74f511d89e0bdc20/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98", size = 251854, upload-time = "2026-09-30T04:37:41.23Z" }, + { url = "https://files.pythonhosted.org/packages/d6/54/f7fbb3493c9f49091213b9c2d6dd65800696f1ce1a3f196a4205f50417b1/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3", size = 248767, upload-time = "2026-09-30T04:37:42.883Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/b3a6385acc5a1e45b39ae9c90bfb9cf838a09b9dd37ef2740ab4c6b4a2eb/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2", size = 274479, upload-time = "2026-09-30T04:37:44.658Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/809913e2cfd279e635a9294fdbbfb1b1dc62a8189d473d561f649fce98d8/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf", size = 258310, upload-time = "2026-09-30T04:37:46.529Z" }, + { url = "https://files.pythonhosted.org/packages/af/a2/f28400ab13359d91bd39179df8e149376b9bf36588e739a3a4f9de2b84b2/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95", size = 271100, upload-time = "2026-09-30T04:37:48.399Z" }, + { url = "https://files.pythonhosted.org/packages/e9/89/9bab37955edf0adb3b66f8a3a6617d9f2f487e0d56f295a6a286cb640aa6/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d", size = 259936, upload-time = "2026-09-30T04:37:50.023Z" }, + { url = "https://files.pythonhosted.org/packages/23/b5/4459e08d45a679f903d50fea08bc52cfa728cca4d7bd02c757b5e5abda2e/charset_normalizer-3.5.2-cp315-cp315-win32.whl", hash = "sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847", size = 186301, upload-time = "2026-09-30T04:37:51.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/e8/55d5fd3935b4bce6da4fe0df61898e8c82653e317e677bd58aceb9c60f13/charset_normalizer-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8", size = 210858, upload-time = "2026-09-30T04:37:53.427Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/974423c2fd8e524c7a7f64318c1e02240ef954912fa2b4d70344107b9c68/charset_normalizer-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a", size = 200309, upload-time = "2026-09-30T04:37:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f9/00ee0195db1013d8f7c416fd770fbeb560bb46eb2e36b054d05cb56f6cfa/charset_normalizer-3.5.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1", size = 395343, upload-time = "2026-09-30T04:37:56.743Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/c00b50e94c964cf934c7899cd47c97952fc11dad71cc5884b3c61795b09b/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b", size = 250114, upload-time = "2026-09-30T04:37:58.607Z" }, + { url = "https://files.pythonhosted.org/packages/50/27/d102dc880bbcffd0479ab64dfc1fb96777a854355a55e2bda72a71efadcb/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f", size = 239997, upload-time = "2026-09-30T04:38:00.511Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4a/bf7ef45794dd293fab5f98a9309817977fbb845b9998f171b8cc5d8437a3/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3", size = 272313, upload-time = "2026-09-30T04:38:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ee/008a2837737991474c5754bb3191010007663860979701990982a502cbaf/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e", size = 269986, upload-time = "2026-09-30T04:38:04.435Z" }, + { url = "https://files.pythonhosted.org/packages/93/ad/bd74a283940dc910c5b14f8e4f80a248082bc9c0fcbe1f54530cb6d9cc5e/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9", size = 260202, upload-time = "2026-09-30T04:38:06.549Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7b/ed341c66f69f688723501fac752be3d63c7159ca0d0d4174fc611e5710bb/charset_normalizer-3.5.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a", size = 259083, upload-time = "2026-09-30T04:38:08.311Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9d/e41588b777965e5031a43128a1e96173ebb35ac75fc53ec3b517e7c21cd4/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115", size = 253081, upload-time = "2026-09-30T04:38:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/81/35/b761eb6d8c1eb218b9b42b9b4d5ac902afdc399fb6dac6f9a9aac7bda589/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c", size = 248712, upload-time = "2026-09-30T04:38:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2c/147169a041b747759f37405c0a97157e8e92de967968373101ff14915cba/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d", size = 274176, upload-time = "2026-09-30T04:38:14.138Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2d/0ff8db0d373ba8538db686db11cd7e8912031490b9e4f383b41912e8d594/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d", size = 259413, upload-time = "2026-09-30T04:38:15.841Z" }, + { url = "https://files.pythonhosted.org/packages/8a/8e/b4a085fb47c9d3a7e43576a4784fdd8fe23f907514a972de8086edaf7a48/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4", size = 269434, upload-time = "2026-09-30T04:38:17.626Z" }, + { url = "https://files.pythonhosted.org/packages/83/1c/d8d8d7322a7c3eecdf3237a4a419cf41d2eaad8e006ce7dfdd9d4c8fa2eb/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b", size = 262716, upload-time = "2026-09-30T04:38:19.214Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/0e4c6ba9b44e97a2da150e52d331e8f9c968b21b358fbffa6c856cebcd89/charset_normalizer-3.5.2-cp315-cp315t-win32.whl", hash = "sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800", size = 197159, upload-time = "2026-09-30T04:38:21.037Z" }, + { url = "https://files.pythonhosted.org/packages/be/33/e90bc2b1374f7f36ef106f56620de5a783907e19ca857efe2277e31cac3e/charset_normalizer-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21", size = 221322, upload-time = "2026-09-30T04:38:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/66/89/dfa6dcb08c200b7830ab56439e8c1890f2971d51aafbb3937894a2e7fcfc/charset_normalizer-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58", size = 209192, upload-time = "2026-09-30T04:38:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ab/176fbfd5b64939c55d652366aa5b9ef1d767af207a3aa6ebeb0d226c484d/charset_normalizer-3.5.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd", size = 331815, upload-time = "2026-09-30T04:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/7e/84/371eac6b30bdbcbf2d632a1a01809103459216fcaae61b8b8d922c1bfb8a/charset_normalizer-3.5.2-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7", size = 253276, upload-time = "2026-09-30T04:38:28.032Z" }, + { url = "https://files.pythonhosted.org/packages/43/6f/c4fbae58febff71709c51bc7e18fdfa55341dc382704740f9f0cbf03817b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f", size = 241239, upload-time = "2026-09-30T04:38:29.732Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/458c3f42164a07d0c5210798e9e704b39e540a6793b05aba67f3a35243a9/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93", size = 231121, upload-time = "2026-09-30T04:38:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/ab9e89367076f6331bb6c65c4bf14a5361fa5191cb6561bf534f18504e1b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade", size = 260350, upload-time = "2026-09-30T04:38:33.239Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/061431ecc688d9d76602502cb57cc01e691e682c18f1beb45f9673b5bbd2/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0", size = 255430, upload-time = "2026-09-30T04:38:34.865Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1f/20c8949f0676f7ab811abdeb7f4d7f1cbc6e61ff20bef08b44edeb092bc8/charset_normalizer-3.5.2-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26", size = 250612, upload-time = "2026-09-30T04:38:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/46f2fa4c431fc98c4ae76a8cb5bdca54e0341e3cfc3fcfd8e82740250818/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011", size = 242083, upload-time = "2026-09-30T04:38:38.26Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/559be29a0c0f086e0bba6922babd38916cc5e0b58ced4de13ee01ea05508/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621", size = 232738, upload-time = "2026-09-30T04:38:39.81Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6c/387b0e4f756a282831c1d9fc6aeb6c51ca4507ca202767c8de15ce9b12e2/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4", size = 260703, upload-time = "2026-09-30T04:38:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/1fdf015f09ef449f50d3ac4b67c90887c9c318b727daa95cc4f866e6521d/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e", size = 247622, upload-time = "2026-09-30T04:38:42.937Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3c/8e7b8a5671ad5d433669fb2a76f1a0164df2d9b1718b0206bc2a16d840cc/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c", size = 257500, upload-time = "2026-09-30T04:38:44.604Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f0/45b579df5cabc1d5d53ea1cc35e8437d3ca768c0acccc7041517cb6fbb32/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0", size = 255100, upload-time = "2026-09-30T04:38:46.289Z" }, + { url = "https://files.pythonhosted.org/packages/31/68/fdec18a343f5fb3f310588dd478b09ac4799e0b187dbade3a8cd776f03ef/charset_normalizer-3.5.2-cp37-abi3-win32.whl", hash = "sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf", size = 174499, upload-time = "2026-09-30T04:38:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8a/b618149cc5207943a0242068d7a27897f56a62947b5a039085f2a22029f8/charset_normalizer-3.5.2-cp37-abi3-win_amd64.whl", hash = "sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036", size = 200092, upload-time = "2026-09-30T04:38:49.707Z" }, + { url = "https://files.pythonhosted.org/packages/03/cf/4c66866fa9e2b1c78e3c911516d1de497a677b7ac60f1eceda74ce777ca3/charset_normalizer-3.5.2-cp37-abi3-win_arm64.whl", hash = "sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e", size = 294363, upload-time = "2026-09-30T04:38:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ad/d07d7862a62ffa6d79d68074d14823243dd235a77c45262acbf6adeb28bf/charset_normalizer-3.5.2-py3-none-any.whl", hash = "sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685", size = 68872, upload-time = "2026-09-30T04:39:21.828Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "cryptography" +version = "50.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/56/d194340cc4a57535e82e1bee9e89667ac4b7c13b5d3f59686deae3094dd5/cryptography-50.0.2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fa8f5efb344d6908a1ce62f4a24e2e5780f825d6f53f5f50ec5ffacac72936cb", size = 3914904, upload-time = "2026-09-30T14:43:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2e/7212ca32fd43dc91f2f41db20160b268098874b4c9a0e7be94d6835f5b2e/cryptography-50.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a582ab2ae1d34f67112cadc86702774c9ea4374df6bca6afe672817203c99134", size = 5367029, upload-time = "2026-09-30T14:43:52.911Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" }, + { url = "https://files.pythonhosted.org/packages/8f/30/e86d7d518489b0ae2497091a35287abcb1a2ce4037837a34afbe9b1d6964/cryptography-50.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f265528741e048bce55c3463ed721fb0aa45a5888d8add8cfeccb3035451bbdc", size = 5324103, upload-time = "2026-09-30T14:44:01.901Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" }, + { url = "https://files.pythonhosted.org/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4f/adfc442765721292fff86d314ce385d3249d22db42295c0dd057727b60f3/cryptography-50.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:7afa5a6602a9f29af1f3a2965f831bae7c9d5d597b7cbb716d41ab3b7d89879c", size = 3824692, upload-time = "2026-09-30T14:44:11.671Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/52eb3770c0d0be2702a98c6e96065ddc0a2877cf0845aa9c23397c142cd4/cryptography-50.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f785f6161f202ab04d8ca194158968798e480ca058943907972da5f12e2881e8", size = 3892731, upload-time = "2026-09-30T14:44:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" }, + { url = "https://files.pythonhosted.org/packages/f4/37/cce70f150c432914460157a6ecc161752e053aa5ec0ef3b3f7dc6e31039a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:94e5e9f108ee10471288214d3d233fbfbb492840a8457eb85178d643ddeb32c7", size = 5328676, upload-time = "2026-09-30T14:44:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" }, + { url = "https://files.pythonhosted.org/packages/30/96/01546c7f69ea0e2ab790a2e4f0934a4052fb9b388147fbf83c2fd72f1e57/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:84f964e537f916e2cc85199e5a88742e964939b575ac8598b3f9d6cc416cdaf1", size = 5285085, upload-time = "2026-09-30T14:44:30.704Z" }, + { url = "https://files.pythonhosted.org/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" }, + { url = "https://files.pythonhosted.org/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cc/1d33befb3cd7ea7e77d2d73f43f2066471da1b21f24a6156efcaabf6d2e8/cryptography-50.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:78198641e5be9521beea5aa782bb551a58068d10e6eb04c9c680c1b69f2e7d45", size = 3795868, upload-time = "2026-09-30T14:44:39.71Z" }, + { url = "https://files.pythonhosted.org/packages/2d/49/93f6a6e7a87c9aa68d44d3e1cdb5fe8f60c90d5d2f46acae9a56892816b8/cryptography-50.0.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:edc3342adf8f697fc5f59c887a304356f147b397809440ed64e2fa6af2f50f37", size = 4133708, upload-time = "2026-09-30T14:44:41.807Z" }, + { url = "https://files.pythonhosted.org/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/9ded6d51ddd9897f6b6e81fb9ebea7951d7cc5d6c890b0ed8abf77a51a80/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_ppc64le.whl", hash = "sha256:b13478603dcd0a2479ff8e87e2c19a7d525734686fe3c49542472293a204212d", size = 5548822, upload-time = "2026-09-30T14:44:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" }, + { url = "https://files.pythonhosted.org/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/196f112617fb08eb4d608a2a6c422373d46f9cc2857f38fc0667033c0899/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_ppc64le.whl", hash = "sha256:cbc8738fd8526d80f35cb3a40d41f41a2e7030bb3b18b09a6778ef63d291c2fd", size = 5506351, upload-time = "2026-09-30T14:45:02.267Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" }, + { url = "https://files.pythonhosted.org/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/55f8a3f2ef5d1529e16835ef10cf0fe3d559ce237b46dddc440c0bba3649/cryptography-50.0.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:c423ab384a46c4dff7217b2ea5ba2e11cffdeab6441acd04cf65a369caf0366c", size = 4111557, upload-time = "2026-09-30T15:29:20.155Z" }, + { url = "https://files.pythonhosted.org/packages/23/ad/ac987755d00e1e64273760228d2635ae38dae2be83e3c6e0d3289d91dec3/cryptography-50.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0ec5f09541743261e66e291b4a0cbf0fb2997aeaab6d9e9c740b9dba1b58d1c2", size = 3943588, upload-time = "2026-09-30T15:29:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" }, + { url = "https://files.pythonhosted.org/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" }, + { url = "https://files.pythonhosted.org/packages/fc/35/b345bdfa40c9126df1a9d33236aa98418367931b8725f84fc3ae2b98dc59/cryptography-50.0.2-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:58ddb5a8e3179d12f19e4ea34d2d32e9d63a4baa142c875c1eb59f41b7243acd", size = 5382217, upload-time = "2026-09-30T15:29:30.589Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" }, + { url = "https://files.pythonhosted.org/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3f/86ff33ce34cc0de6847fb96e035a1a760d81652e38643f617c02ad32ef7a/cryptography-50.0.2-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a6557e5f38e065ca9fbdaf7cfc7435ecb1d113aa81a022d1b51921ee7432e227", size = 5338560, upload-time = "2026-09-30T15:29:39.053Z" }, + { url = "https://files.pythonhosted.org/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "dnspython" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/4a/50822184bd67cc6493f0fb6a880749158fcd31ab3fa07409acfd91f9fc85/dnspython-2.9.0.tar.gz", hash = "sha256:b44dc6b18f07a8b1c56676a19fbfdb5209415b046a9cece286baafa87ff3f7f1", size = 423560, upload-time = "2026-10-09T00:07:24.352Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/02/cdcc9b7c051786a103c3b09e1003a82fa0c66bcb91ffbdabcfbf7b4163b9/dnspython-2.9.0-py3-none-any.whl", hash = "sha256:9a4aedb833c3c1b49214d04d44d3032ab7a9135f7c1d29a549b4ff78fd82fda9", size = 354822, upload-time = "2026-10-09T00:07:22.622Z" }, +] + +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + +[[package]] +name = "flask-cors" +version = "6.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/03/4e464a50860f9adf08b5c1d3479cb8ea1f12af2aa69535c7042c6e628135/flask_cors-6.0.5.tar.gz", hash = "sha256:30c5031552cd59f620ac0c8211dac45b345d3b2df310e7721879e4f46ef9c601", size = 101386, upload-time = "2026-06-08T20:20:17.765Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/55/5bb1a2d918e9f02f131e47a59032bae70e48050e986e941511fd737a935c/flask_cors-6.0.5-py3-none-any.whl", hash = "sha256:68fcf75693e961f3af26683b23c4b9a8fb6b64de17d20d0c37b95e8de7ab2ed8", size = 16692, upload-time = "2026-06-08T20:20:16.247Z" }, +] + +[[package]] +name = "flask-pymongo" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "pymongo" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/22/0b3dd3989ab1636e2fb97f4760ffd858ef77eaa0e84779e2c997f6da9c04/flask_pymongo-3.0.1.tar.gz", hash = "sha256:d225b51c21ceca2e670e6cca79b5c584ad17b96252b48e84e3b423ddb73304cc", size = 7280, upload-time = "2025-01-29T23:45:43.991Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/0b/5142f1b76332c0121c85a9fb5a10dce40bb10d586eea4fda3711abb6c2a4/flask_pymongo-3.0.1-py3-none-any.whl", hash = "sha256:c75a4a9d7f4dc41ddefcfe75d2a0a259049071d285242c1338c42c64dd1223d9", size = 11061, upload-time = "2025-01-29T23:45:41.841Z" }, +] + +[[package]] +name = "google-auth" +version = "2.62.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/b9/4b2528f30114b106e3c3a7298ba38d85e682fd1f83fe7c4a5b77c7677082/google_auth-2.62.0.tar.gz", hash = "sha256:0bef0ce54bdf9ce226c5d66e4264413bd918141c31bbe49fb52eac882f513d69", size = 398180, upload-time = "2026-10-12T19:20:48.328Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/a3/87fe9b7203ec2e56ea6a57c75e6d632eaf9487c7f7978cdad1d801bee0af/google_auth-2.62.0-py3-none-any.whl", hash = "sha256:4ff4319aeb4ad128409759d397a9fcafad126d0031d241cc0dd6b9a00b43e3f3", size = 267911, upload-time = "2026-10-12T19:20:46.355Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "google-genai" +version = "2.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "google-auth", extra = ["requests"] }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "sniffio" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/69/6a3d3839ab56ec17d7923c8679b38955e286e0a9793df10ce6d13bc5f122/google_genai-2.31.0.tar.gz", hash = "sha256:71518537de2ba967a61960cba844691dca3dd4ff07726c01a6917d132213541e", size = 745679, upload-time = "2026-10-14T23:52:24.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/64/1e9ab799830101ccbdcb67376a3f12b59e51f57643862999fd31af6938a1/google_genai-2.31.0-py3-none-any.whl", hash = "sha256:c87b9d495a24b09dedb29b852a201bea69ecd0b3f6c2561dc68f4055451b1b90", size = 1208245, upload-time = "2026-10-14T23:52:22.766Z" }, +] + +[[package]] +name = "gunicorn" +version = "26.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/8a/e4ef6ee11701b6cd64702848415ffb69eeff85cb388a3c6c7fe86f22f3f8/gunicorn-26.2.0.tar.gz", hash = "sha256:62b864895d9ebff0b2f9867ba04fe811c93121596540830c9c916d0769668447", size = 787921, upload-time = "2026-08-24T15:05:59.3Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/85/7522a52e5e2f42faf1a129113ab63e548c42e103e9af395b7bfe65e403e2/gunicorn-26.2.0-py3-none-any.whl", hash = "sha256:bd249d0b3f7972f7432f0a6b6ff3b3ee2d129f70cd1ff6c09a9dd9e29a2b88e3", size = 228389, upload-time = "2026-08-24T15:05:57.67Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpcore2" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx2" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/1f/8176d92e001f86505424b41664032ae26a882bc9ca41a32c803f373f9195/jiter-0.17.0.tar.gz", hash = "sha256:03e432f226a453851079fb84cd17c6da9991eab723e28d716f14ae3d906e0c12", size = 229037, upload-time = "2026-09-12T15:14:14.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/9e/23065f8e2c7a4c372c1b6f6622e4cfab4dc786cb5150052b1527e6a6a840/jiter-0.17.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:00d783a779c5664e16dbad5e3a3c3a75e128b07dd5f4765159658d9210a50ca5", size = 292210, upload-time = "2026-09-12T15:12:35.613Z" }, + { url = "https://files.pythonhosted.org/packages/ea/81/67b58647560bc82a4490d722caa8561d7a86a9f45d4fa620b7e5fe282c7a/jiter-0.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0619d806e260ecf0c2a64521942c94af5d547c9ec99b55ae4f51b538b5576a76", size = 321512, upload-time = "2026-09-12T15:12:36.907Z" }, + { url = "https://files.pythonhosted.org/packages/c7/07/6658359a25f55927f7f8bf0e16465dee2ccd0b2a1a5208acc0df8972e074/jiter-0.17.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc0288ce39190ee33fe6e4ec73161eed34e7e2da509b525546ca061778d62b64", size = 343897, upload-time = "2026-09-12T15:12:38.189Z" }, + { url = "https://files.pythonhosted.org/packages/46/04/5d50a9f0319cbdc37fd53c27f8c313d46afc34f1b048219ae6d8ea068da4/jiter-0.17.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a52a430d04225ffde633e6840bf2381d34c019ff98526b5929755b9052fb199", size = 326519, upload-time = "2026-09-12T15:12:39.532Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c7/d02517832b29eb8275fdd0f4ce0f17b80f58cc4c3ebecd4d9ace990d633d/jiter-0.17.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37f33d327900bf2879613b3363fd48df97b4232d0c41f54bcf2e790c2fc40a71", size = 341369, upload-time = "2026-09-12T15:12:41.486Z" }, + { url = "https://files.pythonhosted.org/packages/3b/07/499b5f5603501cdd93a73a6a176dfad9c96555a3ae58ca9f8e3acba63dc9/jiter-0.17.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cf564d43c4388149ca58ee571d0f5ccf875e20d1fd4662fd94cc0d1ea3b10ef", size = 352160, upload-time = "2026-09-12T15:12:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/f5/75/b04013c7743269d4533ef4e746fc0ed678a143968dd7448658e3f51daad2/jiter-0.17.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:523c499235fb65add25d4bb01b1c4709ce695efdc7deb6c0a7bc515b5c44e0fb", size = 345018, upload-time = "2026-09-12T15:12:44.192Z" }, + { url = "https://files.pythonhosted.org/packages/1d/96/cbb6fd1e42a77c8412ec4643db95059b30cdfc635e387cc9193e098ce268/jiter-0.17.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:455e4ab35cb2a4a91a8404e08fd3c621bae433922e59bf1c494fe20a426b013b", size = 329244, upload-time = "2026-09-12T15:12:45.491Z" }, + { url = "https://files.pythonhosted.org/packages/15/67/d3be402f398566a379bf40ae65be5c3505b14d9e95e0802a597ddde7ddee/jiter-0.17.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6871973bfbd4408f7f1c632b30bbb5bbd9671c1bc8650af6823e24b7be13709b", size = 335693, upload-time = "2026-09-12T15:12:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8d/98e2c4130b93d64f1d67c89060b928d04102549bf05e64451c9e6024f9ca/jiter-0.17.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:77f6aac0137309b31448c1bdcda4c6c77077664a6d018ece8d94019c68a5a5b9", size = 484329, upload-time = "2026-09-12T15:12:48.361Z" }, + { url = "https://files.pythonhosted.org/packages/78/5e/8da91e49f0fbca37c3489fb4cf3ad6676d4965f00ae5468bca3a2513737a/jiter-0.17.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:93946d89fa04d5ba64dd323a8dd8d901676cb8a3c81d99ae4f6c051a9b4c3f2f", size = 521358, upload-time = "2026-09-12T15:12:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/be/21/5388684a5a38af3557cd9c2424b9827c71809cff24373c75ef9d0d3dfba9/jiter-0.17.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:70f19a2ca8429f91e82eeffb2f51cb87bc2d6e953b009b91a92d29c3a16ccb03", size = 110459, upload-time = "2026-09-12T15:12:51.747Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ad/58b3a93525d2ffca7f54d9dee441381990082bd1172fbeb8d6a3f72a4dc3/jiter-0.17.0-cp314-cp314-win32.whl", hash = "sha256:71dbd74314c5df52a1bccf7b8bca46d14e943af7a2012e73b23f49977ef194c8", size = 185043, upload-time = "2026-09-12T15:12:54.477Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4a/1aa520eb6c359b262c14ff995ca7283837208ddfb1202082ce9d73cf214d/jiter-0.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:ac3c6ee3264d6f5c44c617f90bc7e8b9e1587e7d6708c9d8f811cb65582ee312", size = 227163, upload-time = "2026-09-12T15:12:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e4/5997f648794bd9b499491d0ff480b096cc9a9c65bdba29f57568e6aa1705/jiter-0.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:6219adaf59711ba7063a52496e8ec6d3fa3e209d7827d83eee3b2abc780a1744", size = 183505, upload-time = "2026-09-12T15:12:58.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/84a5ec271d09f7590b6073af5ee4abb44eab4ccace453b7e2c5ce45234ca/jiter-0.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:59bddbe6f9ffecc68d641e1e2d619ce64cf8a9e9eeb74e5c518f74fc87abf1b0", size = 321527, upload-time = "2026-09-12T15:12:59.394Z" }, + { url = "https://files.pythonhosted.org/packages/39/71/9e1fd0045f5920b4c36be35c3f0f0dfd123668684f8ad352619d7aa44183/jiter-0.17.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cb41cd1432f1dc19a231cf70b54d42b2c9f05085155859263fce06fa4d41388", size = 340865, upload-time = "2026-09-12T15:13:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2b/14627fd2bc377f3dd09491bcace6b90e34b4d7fea2f1f3295031ff91f528/jiter-0.17.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd7790aa79c8b518e512ebcdfce9f11d8ef5f30efd43720c8a19a548b39fa489", size = 325412, upload-time = "2026-09-12T15:13:02.152Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/8d5808f7bf0f456bde79e6393587183a0cee5f83d179fe1f7f1eff2ba067/jiter-0.17.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbbfe4e3c21c8166980cddc5bee1a315df082454f007947dfb6fb73800768165", size = 340473, upload-time = "2026-09-12T15:13:03.485Z" }, + { url = "https://files.pythonhosted.org/packages/4f/da/1d8c7c6c4ae6b2423b94a81b6b907d37b28f87664e077427b531bf1b5313/jiter-0.17.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c286860abfe8b100cac1c02e225e5776eb9216edd71ba17cdb237da4af32bc9", size = 350757, upload-time = "2026-09-12T15:13:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/eb/96/c1813dcca15c5a370145a448aaea7d1f83f6f0228a5f1130e79340ee385f/jiter-0.17.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f753eb70b1474a29e635e7542ff7312e6d6b951e0b25e8a2e8c34eeb1ddcd478", size = 345203, upload-time = "2026-09-12T15:13:06.131Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f7/fc61cbcf2992d169ede13648fc3fd8e2d3171a3669dde43cd4db556549ac/jiter-0.17.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eae86b1f027031e39db2e0e9c4842221edb7b8cd474d23f87a79b3bd4b651768", size = 328322, upload-time = "2026-09-12T15:13:07.392Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/46418a3abbdffb7dc41b314200360f24f75faaeb35573e81c92de322cce9/jiter-0.17.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bf350452a43173e69e1fc74847c57a60e3d7515807287f29849baa2a85d8718", size = 336570, upload-time = "2026-09-12T15:13:08.666Z" }, + { url = "https://files.pythonhosted.org/packages/f0/28/b8a55b949be6306df8888e365a8df05441de8a7b11289f6957004302e41e/jiter-0.17.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:da139721f4b7cafdbff580a4f511ea24cb91f4909330c6b926a1ca53836c0a59", size = 482879, upload-time = "2026-09-12T15:13:10.037Z" }, + { url = "https://files.pythonhosted.org/packages/75/3b/21d0afa53ba0680962c39f3eb95ed2946f8793369ed44b0c82b490723081/jiter-0.17.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8079849db9a1371bfd90bad088458a8fb836261879df2233cc9632464ecf64e1", size = 520406, upload-time = "2026-09-12T15:13:11.456Z" }, + { url = "https://files.pythonhosted.org/packages/ef/03/bcbaf8b6b9ea23c2c074411f8ecfbb02d820abac5d0cb8f4e280209174a2/jiter-0.17.0-cp314-cp314t-win32.whl", hash = "sha256:8f770b0c77e5fac482e1ba03ca1a7e18286bfb213d749932a00a7e4cd5de5e06", size = 184434, upload-time = "2026-09-12T15:13:13.037Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b5/5d6ce2c93ef6fe1241b37a9005547f9b6d58db1f07f39fe95807d4b98f51/jiter-0.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c4289293e5278d9314b00f15c37f2120fa51d3d68565292e715524c750e775a9", size = 227392, upload-time = "2026-09-12T15:13:14.933Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4b/1e52baf90187606e33a7b8cfa8f96f5829acd7f01870077eb01059ab76d0/jiter-0.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4dfbfe5a6e1e80a7082af559f66386405025ec278833e0c649f69cbc6e1004cc", size = 182776, upload-time = "2026-09-12T15:13:16.239Z" }, + { url = "https://files.pythonhosted.org/packages/05/fc/efe3ac75564ab10f53517958f5ccdc231fc7334af66c76776cb554a88967/jiter-0.17.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:84963d3f395ef5e9a32ce47155e08a7962fa292c159a10cb98b931cef1416925", size = 292143, upload-time = "2026-09-12T15:13:17.502Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4c/46982118d91f9ffe9714319d21ec4f98d9b7e0cfd9062826c524a54de24e/jiter-0.17.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ffa0380ad091de7d3fc33e17a97ff479851ee18a0a2a3ee56ff3215cdc886656", size = 321341, upload-time = "2026-09-12T15:13:19.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/12/9b1ac6ecc6307049913db54839ddba1c11c1ef72c5a8bbb5514bc3b50d1b/jiter-0.17.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755079792868ce5d4938e83b91a0939b34fb858a1ca65a104f2d771bea57faa1", size = 344383, upload-time = "2026-09-12T15:13:20.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b6/527cc72af836d824e9d4d666e64f0a1ca7eafd662a8da9657b78592172ba/jiter-0.17.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3bf4dc2b84a464117fb097d15a25c58d100d2692888e3b0d92df5b48ed16b7c0", size = 326841, upload-time = "2026-09-12T15:13:21.83Z" }, + { url = "https://files.pythonhosted.org/packages/d1/41/567f98617e88005b249503b933803f633ec6ba2d427cf4cc35e5c832125c/jiter-0.17.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02a360707033d8cef53f7f3480817a1489177a259ec6ec01e98c37e0b922ddca", size = 341354, upload-time = "2026-09-12T15:13:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/40/da/b29cda895b785f7d426e224638a885b6145a08ce853b381f34afe3e88c5d/jiter-0.17.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:300ce01ab0215e3dea4d00090143c909aedc65c0f809b3c07983e1d038f291b9", size = 351985, upload-time = "2026-09-12T15:13:26.526Z" }, + { url = "https://files.pythonhosted.org/packages/f7/5c/8a73829e7389e72ea298a450f2b3cb58e71a3e464b45f6d8753740f1c4f5/jiter-0.17.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746243a080b4ca790b8499af3d7cf9825d5f5987933950cd818e767ee353d826", size = 346052, upload-time = "2026-09-12T15:13:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2f/98d6001026932c095ba440925570123043bed29f5ff56158dfe729a9e81b/jiter-0.17.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:b550585523339b71cb852b811aae49d08d7601ad8ffe9f5dc1562f4c3d22fd87", size = 329159, upload-time = "2026-09-12T15:13:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/94/2e/708dc1d2678f092c31c12754e860cd8353e6a85ecbdb1010157edca0da9e/jiter-0.17.0-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0239520085cac678e77a606fd7e3f1c60c371d719790c5e3807388d3da4354c2", size = 336001, upload-time = "2026-09-12T15:13:32.846Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/75a5ae38862f4eaf0fe2f8a9fbf6484c4890df04c06dcdffc45e36bca61a/jiter-0.17.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:eb2295da7c3769f6719b227a237aa6a5cfa6550e478bc838001b592c57e16575", size = 484281, upload-time = "2026-09-12T15:13:35.333Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/6636fae811c27c7f93e1b11fb5800de6a5c9e4269a27cf718e0b31218ad1/jiter-0.17.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:e088612ff90ebc9247e1a43074b72835804261c47e6a6c01cb3ddcb55360d688", size = 521300, upload-time = "2026-09-12T15:13:37.101Z" }, + { url = "https://files.pythonhosted.org/packages/61/aa/12df7e0b0b1a2602e3d5a5a7104d7d9700f254b400f134a9b50955c4d231/jiter-0.17.0-cp315-cp315-win32.whl", hash = "sha256:0b52d52035b3907c5b1f6277857b29c1cbfc965e24e0f27330dbed83edb591ec", size = 185138, upload-time = "2026-09-12T15:13:38.901Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ec/3dd2e495032cddde05723c1f4c743b67a23e55d2af244692a7f58f0cdae3/jiter-0.17.0-cp315-cp315-win_amd64.whl", hash = "sha256:10f5558eed511b830488003449d942bd75829ad6257dc58cb9a03e596a7777b1", size = 226950, upload-time = "2026-09-12T15:13:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c7/ef85704e0a57e9cadb2babc05f6d7c5df4a1c75da1a6ee31e1986b0099a5/jiter-0.17.0-cp315-cp315-win_arm64.whl", hash = "sha256:fa13acf1046f95df808c64b1310705e143fab87aee73ae00cc42d640867fd2c1", size = 183618, upload-time = "2026-09-12T15:13:41.432Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9a/a4b348349de68762b58d6713973d363ad80a1c741d0bf8def7975f0ecb26/jiter-0.17.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:af2f7501580f274b63c4b2283bc425f5df7edf06ae5b171e5f87d912ff359a20", size = 321155, upload-time = "2026-09-12T15:13:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/c1/70/aebd6d0b5f0677de3a3d0bdc4a05fac949b97c4ede454c8809f180ac7b17/jiter-0.17.0-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10c5349312e5cb02b7a21e123a57665afa895953f05bf252a9dd4c13a572b7ab", size = 340985, upload-time = "2026-09-12T15:13:44.115Z" }, + { url = "https://files.pythonhosted.org/packages/a7/82/4c3b49796b5eb62f3f5046f957683f4ba0135fe1a60957c11180512460df/jiter-0.17.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:86f3f9343a288eb85a81ef20a752b2f84564296636db54a9fff0b5c8deaf1df2", size = 325670, upload-time = "2026-09-12T15:13:45.901Z" }, + { url = "https://files.pythonhosted.org/packages/bc/43/f6341ecb4872202a4ef150486fcee0e1ace4aa3da39b71b82061452cdd3a/jiter-0.17.0-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4607ec7d93355fbc25b8dc5189153cf21d66063b9f9cd04dd2774e6e783f9b6a", size = 340339, upload-time = "2026-09-12T15:13:47.442Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c4/bc2c86e08fa065e03cb2fbc53b367c3640a7d257ef9d877b29118ea636b7/jiter-0.17.0-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10cd64a5720ad7f809ac5466ff1705813f1b6b510f195a73acafba0ac0e1f675", size = 350705, upload-time = "2026-09-12T15:13:48.848Z" }, + { url = "https://files.pythonhosted.org/packages/9d/67/91f12aa111cca6e3a197c3e36bf60a034bf9f122f6d41112a639e44217d8/jiter-0.17.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe9f61bb30174d2f5c8396445c360c96c44e78164d0815dfe627ccf57849574", size = 345011, upload-time = "2026-09-12T15:13:50.215Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cb/9f5556e8f6ec89755fb5a709d8eb8270c9a324e31079eda0dfbeca451b6e/jiter-0.17.0-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:370d8fe5bf201dc6925e8a84c81ac7291f74d9fd1778234fc79d517064a5c76b", size = 328268, upload-time = "2026-09-12T15:13:51.809Z" }, + { url = "https://files.pythonhosted.org/packages/22/98/153f20680fb75781a490fb849940e2b00f95035c7aa054df592f36ed33fc/jiter-0.17.0-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b303d88e6a0bda789ec4b7801c7bad68e27230ba1fe4baffc756d1fbd32dc9d", size = 337024, upload-time = "2026-09-12T15:13:53.095Z" }, + { url = "https://files.pythonhosted.org/packages/af/59/b16c9be3a5035df4466cc72e888188c027562de90a723d290ab6814cb9d4/jiter-0.17.0-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:30793a24a31e968969757c9e08d830cbb15a2cd3c4959b4498b38f4b1c2258eb", size = 482766, upload-time = "2026-09-12T15:13:55.713Z" }, + { url = "https://files.pythonhosted.org/packages/d0/55/667dea313094024bef082175d6bfe8976f90d1c00c926af9df1d8e0eab48/jiter-0.17.0-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:686c93d86f2b426c803024b805bd161a6cd10e9627c23e901640eab646c0ad8a", size = 520367, upload-time = "2026-09-12T15:13:57.674Z" }, + { url = "https://files.pythonhosted.org/packages/21/e3/4b1a43501fb9ed17b01d137e380cb0e8fdcb39a254ce31aa2ab95bc861ac/jiter-0.17.0-cp315-cp315t-win32.whl", hash = "sha256:86d703d9faa1ffc8ae4e9de0fa007712ed2171b5c0d93811a8e2e105ac729b0d", size = 184603, upload-time = "2026-09-12T15:13:59.27Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f2/b8ee0372b6ebdf1bde5cc44495d5291d17f961065f5b48f8616cc67cac2e/jiter-0.17.0-cp315-cp315t-win_amd64.whl", hash = "sha256:42b0260445251b1bc520a63baa94a32d88e0f931fba234f1764db7feb7c72174", size = 227936, upload-time = "2026-09-12T15:14:00.472Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/923a1215daba959aed8355973315cb3f81f53e0d01c5b211870a27b41f45/jiter-0.17.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d47687806f9c54c84ea38733507081337922beca90ce819c7d852dd485bc0f23", size = 182977, upload-time = "2026-09-12T15:14:01.799Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/9b/e422a865e1d5d57d0e509b4e0bf1c1a70a7f6382c29a5aa428df994c8bc8/markupsafe-3.0.4.tar.gz", hash = "sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6", size = 153777, upload-time = "2026-10-02T23:07:22.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/c3/a944f3b0df22bd129e96915b9f4e98d2eeca6516687d7618304a966c3c74/markupsafe-3.0.4-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed", size = 12787, upload-time = "2026-10-02T23:05:30.971Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d6/a44863f69d88b6c7e27889108f70d47aed259edf89d5df3c5fca1eac87d6/markupsafe-3.0.4-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59", size = 12525, upload-time = "2026-10-02T23:05:32.263Z" }, + { url = "https://files.pythonhosted.org/packages/17/8f/168ba80e532dd6a93f96f8f706f1ad41d7990b6e1aeedc1cc0d211a33497/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453", size = 11589, upload-time = "2026-10-02T23:05:33.251Z" }, + { url = "https://files.pythonhosted.org/packages/32/b3/aa2c95a574d3af39403a469b295886eb9b6d448da568cbebb5a2cbfdc2e5/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b", size = 11801, upload-time = "2026-10-02T23:05:34.315Z" }, + { url = "https://files.pythonhosted.org/packages/60/d0/34b810107d83840e768bf485de795893ebbae35b26ab061b487adfa0a692/markupsafe-3.0.4-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6", size = 11447, upload-time = "2026-10-02T23:05:35.302Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ab/2f8488f0f817a39fca068d2b17daf446bf5cdb3eae28c3720af534d873b4/markupsafe-3.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634", size = 11722, upload-time = "2026-10-02T23:05:36.363Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/e2d117b048d47282ade906fbfd92814cbee5647afc13fda88a3406039372/markupsafe-3.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f", size = 12061, upload-time = "2026-10-02T23:05:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a8/73a81135e85ba66217f5af7facb03bbb386807e1a729ab64532e4c802652/markupsafe-3.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9", size = 24434, upload-time = "2026-10-02T23:05:38.407Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/fa9216dd01efee2dfdacafe7df32b4d0170fbac694b0c258a193d6e53999/markupsafe-3.0.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f", size = 27183, upload-time = "2026-10-02T23:05:39.581Z" }, + { url = "https://files.pythonhosted.org/packages/fa/4e/a469509e538d37af51103b17b073126973f2b1cbf197ff32c7ddf025cfe5/markupsafe-3.0.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c", size = 24813, upload-time = "2026-10-02T23:05:40.671Z" }, + { url = "https://files.pythonhosted.org/packages/8f/db/d7282caf7ab03af44d5d6fdbaa019b35c7d7f1c90588b839c07cba640d6a/markupsafe-3.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300", size = 23049, upload-time = "2026-10-02T23:05:41.864Z" }, + { url = "https://files.pythonhosted.org/packages/30/f3/b6a425206e6964efda6acee544d0eb01d1501784d0b8e2dcc74986f33b17/markupsafe-3.0.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0", size = 22088, upload-time = "2026-10-02T23:05:43.014Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8a/84d3582fc1f0d5bd466cdf2eebf175e172158a6e70701aacec1de1b35430/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977", size = 23846, upload-time = "2026-10-02T23:05:44.098Z" }, + { url = "https://files.pythonhosted.org/packages/1c/65/db101cce51b7ba4864ac491a9859d297dd1adf0e55b103fee9db9c47c527/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7", size = 22674, upload-time = "2026-10-02T23:05:45.23Z" }, + { url = "https://files.pythonhosted.org/packages/e0/49/ddee9813d71db0c7a5c9d97c832125e6758a0c844777f1cf076569bb0e22/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17", size = 24104, upload-time = "2026-10-02T23:05:46.398Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0e/7d8518d726726870a2399d69fd30d0fa36c5e57a2132c336b58d7c491073/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c", size = 21637, upload-time = "2026-10-02T23:05:47.48Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b0/b505e8a361ba557dbf3b3aa7331ea39b00d2022a26e925ff8463b9714bb3/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4", size = 23081, upload-time = "2026-10-02T23:05:48.611Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ea/9cc3cea873f980c75cbdb6f4277ce30ee955de38be0b3d02f14c108e0698/markupsafe-3.0.4-cp314-cp314-win32.whl", hash = "sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c", size = 14272, upload-time = "2026-10-02T23:05:49.707Z" }, + { url = "https://files.pythonhosted.org/packages/80/f0/5792ff768a410f93ee3f84fc19345295ffc352d2c936b424cb37e514714c/markupsafe-3.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe", size = 14501, upload-time = "2026-10-02T23:05:50.788Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cf/3d074a8edffcc6899355232ff2543ae8d929733239596423b7db79698bc9/markupsafe-3.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a", size = 14364, upload-time = "2026-10-02T23:05:51.857Z" }, + { url = "https://files.pythonhosted.org/packages/d9/31/87ce42159aae2163cf3bbbd0c44bc87780510eecab1ea3859099aed95dcb/markupsafe-3.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2", size = 11759, upload-time = "2026-10-02T23:05:52.951Z" }, + { url = "https://files.pythonhosted.org/packages/5f/53/b047207eeb7752e960aca3eb1df5fb7eefa7dd4c62ac49bb156456c8a702/markupsafe-3.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977", size = 12096, upload-time = "2026-10-02T23:05:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/4326c88a13c7b755657d44b4bb986f8c3d9843ecba7e22d98661d87f9a57/markupsafe-3.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289", size = 25699, upload-time = "2026-10-02T23:05:55.15Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bb/990581b7474bfcf2cf34bed6ba5ea23bd87adb9d671213d68e88620e7a6b/markupsafe-3.0.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe", size = 27804, upload-time = "2026-10-02T23:05:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/6b/89/89491878c28e8291f5aa2fffe2c2d57230d10ae366d55dd810b840513d78/markupsafe-3.0.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a", size = 25912, upload-time = "2026-10-02T23:05:57.416Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/680998b54efdea06fc114565cd739b6d059f826a0279219b218dfa750d29/markupsafe-3.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733", size = 23623, upload-time = "2026-10-02T23:05:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/ae/75/2709f5ac5de9467b40b10e2bb8f89cc63dfb74582e09aa734b1124a217de/markupsafe-3.0.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34", size = 23301, upload-time = "2026-10-02T23:05:59.94Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c8/39eadc6c5b14c9c7679bfb98f4d4c6a97863b5beb91839aca4d2d6e16e55/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978", size = 24950, upload-time = "2026-10-02T23:06:01.289Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5e/01037f8a43e8ccb0bffb4fbdc5212db05bf080fdd7286cd392332d58128a/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc", size = 23330, upload-time = "2026-10-02T23:06:02.441Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f4/23e83ce0596bb0cbe670502d31df8f757bbd01a392aa486fa3b40d1ed399/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc", size = 25239, upload-time = "2026-10-02T23:06:03.579Z" }, + { url = "https://files.pythonhosted.org/packages/88/5b/3708897368073cc683d524750474f41a77d2986152c380dcc55b20fdf340/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932", size = 22767, upload-time = "2026-10-02T23:06:04.699Z" }, + { url = "https://files.pythonhosted.org/packages/c6/61/ebda1307864b409e6b3115757a3d4a09cca46cfb6cc65191b5de226b424b/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6", size = 23668, upload-time = "2026-10-02T23:06:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/09/15/98075cceac3b5ba0dbb8e4762a847be967d2befc349a2cf2d0ac77f62c9d/markupsafe-3.0.4-cp314-cp314t-win32.whl", hash = "sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691", size = 14301, upload-time = "2026-10-02T23:06:07.109Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a3/768b560fcc4156685cb563d922b217810cfa7bc135773367f62f1f9d2078/markupsafe-3.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464", size = 14526, upload-time = "2026-10-02T23:06:08.276Z" }, + { url = "https://files.pythonhosted.org/packages/93/63/da554b4c97a6b0ea3229ca7fe8cbfb620be81613d517f482e85958550537/markupsafe-3.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c", size = 14421, upload-time = "2026-10-02T23:06:09.402Z" }, + { url = "https://files.pythonhosted.org/packages/a9/30/54d11c8ca027114898cab97421fb39e4ffd9ddf47cdbc44df2ec76722da9/markupsafe-3.0.4-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65", size = 12791, upload-time = "2026-10-02T23:06:10.485Z" }, + { url = "https://files.pythonhosted.org/packages/10/6d/97c913e253a14bd3cd0e15a5c56d13203b823fa7ee32498342896a072dc4/markupsafe-3.0.4-cp315-cp315-android_24_x86_64.whl", hash = "sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163", size = 12527, upload-time = "2026-10-02T23:06:11.834Z" }, + { url = "https://files.pythonhosted.org/packages/26/f9/b86d032042a4d597d9e1997f0e5f63a3eedaf11258e0a05760b0a0a826ea/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92", size = 11602, upload-time = "2026-10-02T23:06:13.122Z" }, + { url = "https://files.pythonhosted.org/packages/f2/dc/73c14c1eedf0ac5fa3292ba43435e6c49d2c2050f33cebde541f8f4807f1/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a", size = 11817, upload-time = "2026-10-02T23:06:14.227Z" }, + { url = "https://files.pythonhosted.org/packages/8f/69/2c2fcaa5fcee22d72c7819c0d536fd181c74a688e6143845419579cd2863/markupsafe-3.0.4-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429", size = 11446, upload-time = "2026-10-02T23:06:15.574Z" }, + { url = "https://files.pythonhosted.org/packages/88/54/9e5ec76c62e6e2834d5a93623018c943e8b3bb41d663e3fd4c03303b9b85/markupsafe-3.0.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8", size = 11719, upload-time = "2026-10-02T23:06:16.701Z" }, + { url = "https://files.pythonhosted.org/packages/96/24/3ec292b44064c16229e064d770b2625bd8ea941aa61f44905a9fa44942c0/markupsafe-3.0.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97", size = 12075, upload-time = "2026-10-02T23:06:17.855Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/b64fdb1f304848518742136983c24e96d967bfb59a0ea160e92736901ab0/markupsafe-3.0.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b", size = 24656, upload-time = "2026-10-02T23:06:18.963Z" }, + { url = "https://files.pythonhosted.org/packages/9c/18/23997d4c65b355da6390d61cd56e0ab3befd6ba8dda25cb40c602bd0fa6b/markupsafe-3.0.4-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9", size = 27737, upload-time = "2026-10-02T23:06:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/35998dead3c6af88c38265a56e58100211f036234ab88eb2283fd4cbce44/markupsafe-3.0.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653", size = 25003, upload-time = "2026-10-02T23:06:21.284Z" }, + { url = "https://files.pythonhosted.org/packages/82/96/ef49135ce260db4ca4a12b119ed468449cd248db6b1468e2112b546d7a2e/markupsafe-3.0.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369", size = 23111, upload-time = "2026-10-02T23:06:22.524Z" }, + { url = "https://files.pythonhosted.org/packages/50/7d/83126e338bd88c17a220668235368ad719fd4638e426739858cbb8508f77/markupsafe-3.0.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19", size = 22403, upload-time = "2026-10-02T23:06:23.785Z" }, + { url = "https://files.pythonhosted.org/packages/83/dd/daf7e420de23c8206c365204e7b85e1251d8e19d34196a56336f316e5ed2/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e", size = 24083, upload-time = "2026-10-02T23:06:25.037Z" }, + { url = "https://files.pythonhosted.org/packages/19/3c/11eecdc06bc44ad5570350085b572ebf049e8f9a38d1ece6d76640b739cd/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811", size = 22931, upload-time = "2026-10-02T23:06:26.328Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9e/ac0fd77f2a726e56ecc3ca0235d095feace1358d1b822406c2a2ef26a4dc/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea", size = 24299, upload-time = "2026-10-02T23:06:27.742Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/c6bd842ad58ff5b3bc76eeed7e9a42a6f11adc5d090ec697b72c9672731e/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916", size = 21948, upload-time = "2026-10-02T23:06:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/82f586711fed61e86faa1ee1bc317d68cd45a10c8bdbe3f7d1fdf9026ad8/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741", size = 23150, upload-time = "2026-10-02T23:06:30.583Z" }, + { url = "https://files.pythonhosted.org/packages/19/2d/2dfdce99318abbfa26925195fbc17db188c46a1ec6457be121b6f9cfeb42/markupsafe-3.0.4-cp315-cp315-win32.whl", hash = "sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b", size = 14269, upload-time = "2026-10-02T23:06:31.949Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ec/6000fd82e8791e58fcd0456ec20f098957e2b03d5ed02eb73241a577c0ba/markupsafe-3.0.4-cp315-cp315-win_amd64.whl", hash = "sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214", size = 14496, upload-time = "2026-10-02T23:06:33.258Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/e73bd5016421d5d6e2fb6de7dd609f9de020942ac8c626526bd8c6eeaf82/markupsafe-3.0.4-cp315-cp315-win_arm64.whl", hash = "sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67", size = 14361, upload-time = "2026-10-02T23:06:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/90/df/cb8c3dc98d313a951df2f8968f44e4cb5643df6d3cab749a530ce2f7d972/markupsafe-3.0.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad", size = 11760, upload-time = "2026-10-02T23:06:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/d6/bb/4af9b3ca0753d654ac75f9531d5bd741bb77ca6e696f36807c475ffc099a/markupsafe-3.0.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99", size = 12108, upload-time = "2026-10-02T23:06:37.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d4/b56429313aee5fd59b079c3df5615299959e25e7113eb6d8caadbdd7d38a/markupsafe-3.0.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002", size = 25762, upload-time = "2026-10-02T23:06:38.419Z" }, + { url = "https://files.pythonhosted.org/packages/65/f5/34c181e891aa4f7d59c918584672e0c5eb7fffe76c1387d1246008bf4081/markupsafe-3.0.4-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e", size = 28400, upload-time = "2026-10-02T23:06:39.819Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b5/ad14694fd0ac9a5ce30bc6498f2999378f418583dd1679cca5a1b512957e/markupsafe-3.0.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c", size = 25946, upload-time = "2026-10-02T23:06:41.381Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a8/26b606445387d0ceb1eb1f21840094b84e4e3c3c3983d80d10b89823b490/markupsafe-3.0.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8", size = 23634, upload-time = "2026-10-02T23:06:42.748Z" }, + { url = "https://files.pythonhosted.org/packages/39/a2/b8814de672f1f0094d498bf646f2fec9d6356b503d28ef500b71c5095377/markupsafe-3.0.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe", size = 23488, upload-time = "2026-10-02T23:06:44.176Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/287223376fb73335a3cc5d6eb22c6ab01358cf33945a9c39c06b9dac3f4b/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2", size = 25037, upload-time = "2026-10-02T23:06:45.646Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/4df8355e313426d19e62ba33e0253c009ca12a0894ee77d67fa67255361c/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38", size = 23543, upload-time = "2026-10-02T23:06:47.264Z" }, + { url = "https://files.pythonhosted.org/packages/71/e5/8377731e8495668dcc768f645e717df18318c841edaf023a99395f6da9b4/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494", size = 25282, upload-time = "2026-10-02T23:06:48.795Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5f/373456e37ceb1478d657d6fe769cbe0a39f0a8dfc1548eeb19c471eefdd9/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d", size = 22993, upload-time = "2026-10-02T23:06:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/2cbd5628435afb6f541bbaced4bce0c2edac4b09a142e6e928b8b0da9858/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894", size = 23712, upload-time = "2026-10-02T23:06:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/157e10966b033b363aeda5263e82596ee232a0b1d082fdbf90aa417ff083/markupsafe-3.0.4-cp315-cp315t-win32.whl", hash = "sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78", size = 14300, upload-time = "2026-10-02T23:06:53.241Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/55884815414c9706a23deca150b72c25a62109e65b0b6ce232077802c719/markupsafe-3.0.4-cp315-cp315t-win_amd64.whl", hash = "sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c", size = 14522, upload-time = "2026-10-02T23:06:54.729Z" }, + { url = "https://files.pythonhosted.org/packages/92/f9/ecbde7149e95b8a0f18e16d5d747f7dc06049d5da2e4f77f6f5e4a1f46a8/markupsafe-3.0.4-cp315-cp315t-win_arm64.whl", hash = "sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba", size = 14417, upload-time = "2026-10-02T23:06:56.246Z" }, +] + +[[package]] +name = "openai" +version = "3.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx2" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/f7/17bcb5033c79721d11b8815a7822575e6dedb5af65149575b56c21eb43ae/openai-3.32.0.tar.gz", hash = "sha256:6feb4a50aa653df6d2cd5e9118d5f079a51cb653d652746d344164a3e8419c89", size = 2227852, upload-time = "2026-10-16T20:18:14.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2d/3acb5ea3c158ecb205d73d951d8b3df263bc7c927c25a687e06918fd8c07/openai-3.32.0-py3-none-any.whl", hash = "sha256:dac6033390174ee85a3115e312a3a4fabc71824711ded5b3a79d5b670dd6b562", size = 2256256, upload-time = "2026-10-16T20:18:12.544Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/a8/c5fdbeee588bb8ada9458774f43adf1bdd30bd59157055142183e769a024/pycparser-3.11.tar.gz", hash = "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc", size = 113796, upload-time = "2026-10-09T12:56:59.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/11/0e6f11117525ff0eec40ebac3d313376f102df93ca44ad9e893ee85e4f89/pycparser-3.11-py3-none-any.whl", hash = "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", size = 51178, upload-time = "2026-10-09T12:56:58.131Z" }, +] + +[[package]] +name = "pydantic" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/0b/8e10b2e693af8ec54346a14caf36221334775975a747c9ceaa3f8371d96d/pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26", size = 900362, upload-time = "2026-10-11T18:37:55.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/ea/a56b9fe5066f3537b7882f77e9c5dfb26d8c2eefdaed9b5fc73d57b4dc22/pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454", size = 488106, upload-time = "2026-10-11T18:37:53.437Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.50.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/24/af4ec4be49fbc810f35b0bdcacc3d433b56bbfa469fd3bfd4ae116cd9bf1/pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09", size = 513515, upload-time = "2026-10-11T18:35:44.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/33/28b96e81677153715e3eafb9f26663a80841e859fde282a380359b0d3fa1/pydantic_core-2.50.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1cf41f1ae3fa155cf167a72689ad044bcc1e3c97e064123677149bdfb5dafc4a", size = 2224428, upload-time = "2026-10-11T18:33:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/94/40/15c06410c9b7b8da5805d27b64e09bd3f900e986728106db2689dbc51513/pydantic_core-2.50.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4df197990c15b5a37c5a277d131d9f2c67de6133f2e5dafd80d9bba4b99f46f9", size = 2023740, upload-time = "2026-10-11T18:33:08.763Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4e/5eb629f6efc2a27e421d789dd4bfacbb5f6d09c80c28b13d1e87d73163d5/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0036473f5583e6a60e50b8b21651511564277a3f05cc5dab8cf579f552cd5f6c", size = 2049435, upload-time = "2026-10-11T18:33:10.366Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8e/f195aebec49ad12318876ac2368c197a7939f5d52f8e156d2238ef8a4588/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:992c3514ec891fa7858099183e4d64e6bd5a5d4ff452fae29df22faa77a006bb", size = 2172975, upload-time = "2026-10-11T18:33:12.368Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f5/7ad9fb83010cd5ea0948409db105676ed779c4e709e2d3d89b9fe2558794/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:739dc730e6be3bd5ec2f4ab5cfc7eb047cc45fc1497b3bafec74ff2ed07df597", size = 2364197, upload-time = "2026-10-11T18:33:14.244Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/bf0aab3e78301d202b82a0322ec968cd703b11fc0623fde9a28708936f62/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fad3a91e51b6d2039c572db04a5a873260b399f6bd62c3552671fa7a4a2899", size = 2444259, upload-time = "2026-10-11T18:33:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/38f6d6564fae57d9b12e5676dfa947e0e7d5c46dbeaa7eb3352261d6d299/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b54c2c90ad348b5e3a85e03e715d572c1fde357ef104cdfe3b03b697a404ea", size = 2159857, upload-time = "2026-10-11T18:33:17.51Z" }, + { url = "https://files.pythonhosted.org/packages/65/a0/fb0a3ca10f139dcf765b2d312d10cf0f65c57c59993229d00ad12b14ceff/pydantic_core-2.50.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:2df1ff41884de2bc4b307bafd7c40a691094fad2ff8e767e5b45a319257bcf4e", size = 2233954, upload-time = "2026-10-11T18:33:19.591Z" }, + { url = "https://files.pythonhosted.org/packages/8c/6a/e63842252702aa4ec6e6b7178ca85e541a77459076592c23ebe2d9840b33/pydantic_core-2.50.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe90228920fd8ff2be62622b6bb8a2b11acd65046d50c6b130614b5879605a20", size = 2243876, upload-time = "2026-10-11T18:33:21.393Z" }, + { url = "https://files.pythonhosted.org/packages/39/25/5991cf8318b37e0dfab47b87541619a1df8501a793cba0d978846cba37a7/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:844b869f118e22a41a091bdcedda8a71bc1b0f62c38d1a0c3211cece47e1d8fc", size = 2277908, upload-time = "2026-10-11T18:33:23.324Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3e/3ee8baaa6cc25a6961c69168cf9ff0f002d56f4e0d541ad6724c18fb61f3/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:2eb75304506894a281d346220a4f7481a1b8729577c5ed2a05395991966a8396", size = 2449285, upload-time = "2026-10-11T18:33:24.942Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/acbec6deac13fe697a80c275a9b6661db62c4d323bac8a47347b1f39c7cd/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6b20a4bffabdad0db2927ac034ae3b8a681b1f7a0182f3e60b479ad2fde21ebb", size = 2479013, upload-time = "2026-10-11T18:33:26.925Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/21a3f237b264389f6219d053ee78fc1b5c2fd402c1b1cb2b6cb8b85f9834/pydantic_core-2.50.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:99ba9bc2b8062ea0c326a990f7f00e6530c23579de66dd246e72c4cafef950a5", size = 1484400, upload-time = "2026-10-11T18:33:28.693Z" }, + { url = "https://files.pythonhosted.org/packages/09/ce/077a6d262d12ef09108377ac0717f029f420d773ace63cafd6143af75568/pydantic_core-2.50.1-cp314-cp314-win32.whl", hash = "sha256:cf356f70551d40374eaffb1aa63f1eb6d2006681cbd7a9faea173ce0f4dd7cd2", size = 2103222, upload-time = "2026-10-11T18:33:30.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/4c/350a2415209c43d670eb71d3c040f332c04a30583d39e311b05c7ac15762/pydantic_core-2.50.1-cp314-cp314-win_amd64.whl", hash = "sha256:d32f3acc081cc3923386d88f422cde8892335e95f034e0104bb4cf9310d9915f", size = 2171586, upload-time = "2026-10-11T18:33:32.139Z" }, + { url = "https://files.pythonhosted.org/packages/bf/92/9bea6ca96580a0902fed366f064f0829e404f41889b34543279a1162b888/pydantic_core-2.50.1-cp314-cp314-win_arm64.whl", hash = "sha256:bed5163e03b98bc1fa2eb05d74c63d9c5c95d8ed6254985481640fbf5e237dea", size = 2155773, upload-time = "2026-10-11T18:33:33.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/8c/f121f073cf32bdd5cba7e6230b1ce0ac845b0cf43e44dd597d95af272db2/pydantic_core-2.50.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9572c1369e9c9da2d64a7b7992c786d90ff295abc93964cfe3125e4290768070", size = 2234017, upload-time = "2026-10-11T18:33:35.588Z" }, + { url = "https://files.pythonhosted.org/packages/a4/69/2bb2bcd6146cffe98d760a46c42ae71efd5151d9b2f9c9bf6619a3b32083/pydantic_core-2.50.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2005207aafe1231315718bf6ed5d064a7300fb4772754af35ee72fc68159492e", size = 2001016, upload-time = "2026-10-11T18:33:37.57Z" }, + { url = "https://files.pythonhosted.org/packages/20/b3/fbf854c7d07ec114260c26e9e2071a4381740f9ae09641dbfcbdf2a18c45/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f6047f62a6c5ae08d0a6afb035667aa2d97c3d20d69762e034c5ea144d92a5", size = 2023015, upload-time = "2026-10-11T18:33:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/65/20/6de55b2f92cdb614b745c6e9ced639fc4fd7e1e77604825a88bdece6fcbd/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ef800dd7d85bcdadf4c3076e4c94e43939493558a3b69a1ea830c706d4617bb", size = 2192543, upload-time = "2026-10-11T18:33:41.381Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d9/19e91c94bd5c405945ce2f15526808aea37e162c253160de8ed7bf70b406/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0135bcdcaa0f23573f286e4cb5e0fd2962700964ed13df085b85f2b97aeab9e", size = 2388704, upload-time = "2026-10-11T18:33:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bc/2e24c8415eae1a25ee5a5484946a917123bad2e9d01689a759236928175a/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b3a6f334c6a2345ca15318ff894502a90012536404b37c844a976c76c846e0b", size = 2464118, upload-time = "2026-10-11T18:33:44.856Z" }, + { url = "https://files.pythonhosted.org/packages/bd/1f/945b8053cb061c64e102bcaf7bfb9ed740c0bd4349f9bb978a7d4ddff4ab/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e01fbbfdb9be777b316a71b6c49efaf4a08b615d0a98d678cda3023f79d019", size = 2150142, upload-time = "2026-10-11T18:33:46.661Z" }, + { url = "https://files.pythonhosted.org/packages/2b/78/96a3e50bf0d64aaae781107eb9335b7529d05a85793ed6e7241c4cd1d931/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a29a061fec0b4e2d714f277e70a3a18125ecff803f2fea6eade2f2e53711d112", size = 2245577, upload-time = "2026-10-11T18:33:48.574Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5d/a6038a0322232758a6ebfa709f4ca14a60bf5b93940cd0b345056a557da4/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5187624823423e1d1b82b1072ac41dc837389e18d3d0572cc19bbee46cd550a", size = 2263728, upload-time = "2026-10-11T18:33:50.527Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4b/76ded3333a457a9344c4f2d63f2d82d0101654b05178fa4ddfe7d3627674/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3e46a9eb0a0901dd6275e6b06ac3a464885ef350ec4121fe486869de8053e4bb", size = 2293630, upload-time = "2026-10-11T18:33:52.362Z" }, + { url = "https://files.pythonhosted.org/packages/a9/00/9eca378335c9c1b72bc779bf6e4c2a82ce784f14ec48a0e87102c9870e05/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:756d669f04e62ec4148ecfe22be6a4484d9b1181a6ef32e205ebfd200540858b", size = 2468048, upload-time = "2026-10-11T18:33:54.118Z" }, + { url = "https://files.pythonhosted.org/packages/35/ca/e3832e9cf93651251de43c8c5c029ed680a3c1a0a1b17ee26c81bed08cbd/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c516cc5367ca3448995d42cb994bf3f4c9002d2a7c22eac9622551269ad1b807", size = 2512159, upload-time = "2026-10-11T18:33:55.99Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f2/773469b5a10a39116a2cb17edaf6d722f017dd8da07161b371465311c542/pydantic_core-2.50.1-cp314-cp314t-win32.whl", hash = "sha256:9d1bed94af6a63835461f3cf7502058eb166c58c4778e11d0f433cfb1bd69e19", size = 2098406, upload-time = "2026-10-11T18:33:58.043Z" }, + { url = "https://files.pythonhosted.org/packages/ee/42/0bb74f8f25204b259b11ab7c12dc7f180893b6bd706118b385147fb6efd5/pydantic_core-2.50.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c8dce1f1e0e5358b682a6ad3fa5e31b31d4560997b8e61417e9217c8d60f8a0c", size = 2148481, upload-time = "2026-10-11T18:33:59.913Z" }, + { url = "https://files.pythonhosted.org/packages/47/0d/d801646c9679a4e630e15cf521d4108b93854b42a6e6e02391bd4c6b1095/pydantic_core-2.50.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ceff0acc940be2715bd6ad17b24c0e5304abf44f6efd0f81ee8499e640f9dc86", size = 2174972, upload-time = "2026-10-11T18:34:01.875Z" }, + { url = "https://files.pythonhosted.org/packages/b2/84/23984b763d8862a02a13d27a44b6e8169428fd85ecfed88f54c29108604d/pydantic_core-2.50.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:8a6791afa2245e6c6b180122d105941644f5bd410bb18623b408808cc41a3102", size = 2224599, upload-time = "2026-10-11T18:34:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/4d/90/a63cf8586abc1d1a3f6d9b18f0224789ebf003851f092ebda3c7c863fd32/pydantic_core-2.50.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:84f34323a61a365b4e9295de6028474754829aaddd59c7bf1a040e7487ef8f3c", size = 2024191, upload-time = "2026-10-11T18:34:05.901Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6a/f34bff9808ffb4907fbf5f5040457d253cacf2da7fce9efbc99ca1b1a44d/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23edad659e8dbd8ca7e4e877fe6c81573abbdf215bd25a68b53e1272f58b80c7", size = 2048252, upload-time = "2026-10-11T18:34:07.757Z" }, + { url = "https://files.pythonhosted.org/packages/b2/54/13f419bf1eb59852003818e25935aaf175687d978f4c4bca70e08fe40a3b/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a5fce22f1e87d181e924e12da7d81cfe031fb3881a5ddf26ad28f141756ca43", size = 2173376, upload-time = "2026-10-11T18:34:09.592Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6c/b5a34d24cd0c81669d8f8339d74e6815abcf2f8fb48ab4b49b84c09be1d5/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c73622ef819328873b53109ee4f77ceb598bffedd02daf916102be3228866b78", size = 2364559, upload-time = "2026-10-11T18:34:11.534Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8d/d64d6216a8df365082665927ff923f183056f9049fee08e9777c9ac05296/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce8c25ca38cc0e3d7753ba180808de2c0c8cb24eae0df64491e40921454e9831", size = 2444697, upload-time = "2026-10-11T18:34:13.535Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0d/1b1149f60a00ea21ba5f70e28acbd40feb4598af414f80f53c921fac07c9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7689580e72a642ab5ec64d5f55b2e33636fa43b4ebe63c0c2c965ef307c7d1aa", size = 2159961, upload-time = "2026-10-11T18:34:15.56Z" }, + { url = "https://files.pythonhosted.org/packages/1e/35/f236549299dcc78e71e945495d7ec67e78d20844d0999c60601685003031/pydantic_core-2.50.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d5c0e32fdbce7f1e8ef4d11f655694bf5f4175c757a9f1dc2be09b8864e5bcf5", size = 2234581, upload-time = "2026-10-11T18:34:17.502Z" }, + { url = "https://files.pythonhosted.org/packages/6a/85/26901a490522b7f75ef9bb9a7afb73e5bb550f0e1cb5b99a0069183e2eb9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40f523349960fa30f3ea51404308ff50f9997a90df639590f47a057c1f32b415", size = 2244090, upload-time = "2026-10-11T18:34:19.402Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2c/481bcfc70ceeb77a778ca6e5b705fe592cb64735c108157f81b7dd9c280e/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:d4193206b6587047437f6f11d7e776df23e1c1e23af2a54d9347275614791e10", size = 2277948, upload-time = "2026-10-11T18:34:21.317Z" }, + { url = "https://files.pythonhosted.org/packages/24/eb/f1e09333faa7ba447cde967310f758430cc7c5e987bdab5228816c70030d/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:84bc765b282a9d5b7fe0348b8648904f25a6a04b2139da52b1dd30c8ac3a2c8f", size = 2449688, upload-time = "2026-10-11T18:34:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b3/036bde636db8f76d92996e81aefc75678ab5cec4a07eea1ad0c72a893fc3/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:ed1e728b39a383c81035b2459cfcb35d99dfb01f7d6ebe3a913bc1cc5b81e459", size = 2479286, upload-time = "2026-10-11T18:34:25.214Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/07f018294ee18d144afeb6df1a47d9e92960f014be45c5ed13519fa5af95/pydantic_core-2.50.1-cp315-cp315-win32.whl", hash = "sha256:bc94f474417604bd383d2cd445d071b07dd55fedceed3ce33407bf1fcc107290", size = 2103683, upload-time = "2026-10-11T18:34:27.42Z" }, + { url = "https://files.pythonhosted.org/packages/5f/98/f9bd7e1f9b6709f155acb9ef826d9c3884fe925f811fd8e55b9b52280bad/pydantic_core-2.50.1-cp315-cp315-win_amd64.whl", hash = "sha256:983a662de2571cb2502fc8ff47b6770b03d025d2eb314c92f77b3f07c74720ed", size = 2171681, upload-time = "2026-10-11T18:34:29.508Z" }, + { url = "https://files.pythonhosted.org/packages/10/87/4bb3e1e7f385c076ab5af4d6dd0571b22042cd8207eb810d5b9fef15ae31/pydantic_core-2.50.1-cp315-cp315-win_arm64.whl", hash = "sha256:94845ff54dc5193f228cab81b2662a04bfbb892e95bdc15edf7399000ce57d54", size = 2156251, upload-time = "2026-10-11T18:34:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/83643225b08f2aef6c8cc4bbe6e3f79c5e139c4364a6e450d0f399d87774/pydantic_core-2.50.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:4a53d13cdfbedbfa87f08b83c1a0a5efcc767d785a4b41934fa9cb672670493a", size = 2234363, upload-time = "2026-10-11T18:34:33.65Z" }, + { url = "https://files.pythonhosted.org/packages/5e/66/127ca649ba2f2462039dc1e694c6c01e00a3689f023a617364d3507e6d97/pydantic_core-2.50.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:efbecf43d321f7b9281441f1f213f7c21c66988b0e06c2730ba13ed47a46bb08", size = 2000972, upload-time = "2026-10-11T18:34:36.037Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e421e0a5d653b1203b090b2e48745976988d7338a647940704b5b9c2b399/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1f08f68dac9f9e83845a8039880aba2ab553eb9b2259c3243a313182c253fe", size = 2022925, upload-time = "2026-10-11T18:34:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/ea5568e2491e1a71100f15ae8c2d01ef52db42a184a2d4e716bc79e5eb8f/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5dfe41f232befddb9c4377f6cfc702b51595e2d78ed082672adf8758d2c4619f", size = 2192867, upload-time = "2026-10-11T18:34:39.921Z" }, + { url = "https://files.pythonhosted.org/packages/ae/5e/3b8c3a35acbe219909ada5defad5d7d9fed845fb2b37bd5ec518f453c119/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adc06d218a1cadfd2ec4628424d7d79ce4eba69c2965e7e7b55106f0da5208c8", size = 2388687, upload-time = "2026-10-11T18:34:42.184Z" }, + { url = "https://files.pythonhosted.org/packages/b6/aa/7889b4e515f91a2e8c0ae6b5081fec0feb30c4398d1434e14793c60f173a/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cf91809d0721ab81592ba67bea7694821679c10b1a2e3c3460082b286c1918a", size = 2464415, upload-time = "2026-10-11T18:34:44.384Z" }, + { url = "https://files.pythonhosted.org/packages/08/78/93449e628eb8a6fdcce3eff9043081179d1bc7ce6f1bff32dc5006f41e00/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23923ab9292c40da026330b1ecf4dc2618c8e86e0422e5d1fbf50d94d64ca4f8", size = 2150823, upload-time = "2026-10-11T18:34:46.392Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f1/72c5bc129fceb0d00f05dc1e67f518c1728de1928c55f81fc13d7690de39/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:f3377c8c2b3ce898423c5e5dd94c7982e30aa7717a7e6ab2470b9de364963709", size = 2245806, upload-time = "2026-10-11T18:34:48.805Z" }, + { url = "https://files.pythonhosted.org/packages/28/2a/922a0e78f3aa6ab837b59f88190233fb8dad546c17995fde9bb3ed3b9b49/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:455a773617b5913bf5c20d0692e5787b119e52c4d40ea644ca31f5758fd31be2", size = 2264160, upload-time = "2026-10-11T18:34:50.876Z" }, + { url = "https://files.pythonhosted.org/packages/52/a8/0f1449e3e1b20941c9372faa18e7b6a092e30cdfa841902473f7989532ac/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:1a9006395dece0e32e704c315eff8a00bede494f6108546cfc5539c89fef4f9a", size = 2294031, upload-time = "2026-10-11T18:34:52.876Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d0/1031f492857de70355fb16524bbb03efce5fd34c93ed4a1ec60be07e0d4b/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d2d82aa62521c55ddfb000ae70f88cdd8de974078f6024e821dfe5addd0c818f", size = 2468423, upload-time = "2026-10-11T18:34:54.995Z" }, + { url = "https://files.pythonhosted.org/packages/46/52/269ffffa645b8e47906395a39cf9db1151ae9fa4bcd7f47b960bc31baf37/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:009634b83993777ddcd69cad0ffcace43dabde692109528e35f0fde91e386a8b", size = 2512598, upload-time = "2026-10-11T18:34:57.149Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/e47e28281f20326ff6f3c31d626f0a83e615d2d94ba41bb0ad6ec237184a/pydantic_core-2.50.1-cp315-cp315t-win32.whl", hash = "sha256:3fde4fdc6487a58d944ca87cf5adc95d5f266e872c19599f5f4c0a8a1b1f9f9f", size = 2098535, upload-time = "2026-10-11T18:34:59.313Z" }, + { url = "https://files.pythonhosted.org/packages/03/ad/759e181e69c1b472c60b2049e5f61d5da1deefdd5ce1df4bb2ebcf771f25/pydantic_core-2.50.1-cp315-cp315t-win_amd64.whl", hash = "sha256:1c8632d4ac04e6f91128fca584b3a8a507d81604c24eeaaad00d4be42765c32b", size = 2148645, upload-time = "2026-10-11T18:35:01.571Z" }, + { url = "https://files.pythonhosted.org/packages/6d/56/8a702c27e5be9f47e5f19d8669227424290e4c024e7c370279cbaf244b4e/pydantic_core-2.50.1-cp315-cp315t-win_arm64.whl", hash = "sha256:c3ede305158e75510be50869b319550ab072008c13d64d4ab1e094fb286b6f44", size = 2175297, upload-time = "2026-10-11T18:35:04.079Z" }, +] + +[[package]] +name = "pymongo" +version = "4.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/8b/a9d214044153cb7d9141229d3e1b171cdf4f460fa07cade9354c4ce2f84d/pymongo-4.19.0.tar.gz", hash = "sha256:3c510dd3c5d9b392d3b33bb5d2a594758acfe8f026fca654253f947ce0af9d40", size = 2689381, upload-time = "2026-10-14T19:48:19.629Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/e7/6e62d60303a1e5cc816cefaa4d57d74df8ee65753ee9fe154b5fad851de3/pymongo-4.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:08c354566ab8b5dce6d805f35d61b5575455d3ea1835d7b90151d53e8c32e669", size = 826849, upload-time = "2026-10-14T19:46:56.892Z" }, + { url = "https://files.pythonhosted.org/packages/e7/68/b2f67b99f22c5543a8be397c0ed8dee526c23717b4491405ae513138d88c/pymongo-4.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:06b9ee12c4ceb7fb6ff8a7ab0465814c1cb5e5c6c2c452cb18eab7435b38a5b2", size = 827363, upload-time = "2026-10-14T19:46:58.842Z" }, + { url = "https://files.pythonhosted.org/packages/02/bb/35e17473d000bc0517190aabe1429853aa142499370dbd6d7ae3743e8833/pymongo-4.19.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ec25ab536e42e48fde356c6fc86e66f548e5af0cc584365e2ec34d3683be5a63", size = 1049786, upload-time = "2026-10-14T19:47:00.537Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/83cc2961d977c1ba36662f24ae55c9f5dbee2845ca615146fec0f4eda053/pymongo-4.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e65783e95b37c3387ed1105fe01e2be6b1b394c22331c5e8cc2fed2c3a30a06", size = 1059425, upload-time = "2026-10-14T19:47:02.511Z" }, + { url = "https://files.pythonhosted.org/packages/cc/94/baa32ef582f9edf3112b00f6e271cf5f83c481edcf999e2f462898990e87/pymongo-4.19.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3264b209b6319cae120306e266ed5fa9c7bc071b73ba5e13cbad23a6cbd73d2", size = 1082787, upload-time = "2026-10-14T19:47:04.38Z" }, + { url = "https://files.pythonhosted.org/packages/37/eb/949a24776ceba31e9b731f7048dce4fbb913047afd16580a61723143afb9/pymongo-4.19.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:212dbc97f8e813a24639aaaef38503d84f7652d00b88b391f87762ba4c1f1709", size = 1073579, upload-time = "2026-10-14T19:47:06.247Z" }, + { url = "https://files.pythonhosted.org/packages/5c/b0/a577ab8eff3772cf7036118b4e407a8cbb53add7bbe322f011871eb6db44/pymongo-4.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2faa34469b052635c81dcec6b07fc5757d4aba0ec60f94c6658c7fa6f887bc46", size = 1057865, upload-time = "2026-10-14T19:47:08.076Z" }, + { url = "https://files.pythonhosted.org/packages/95/cf/81b1d8a35ac3e5d5dcd8fc466f9acdd8f67a5035da130afb0d76e2efd6ac/pymongo-4.19.0-cp314-cp314-win32.whl", hash = "sha256:eee3fc70ea4253c8c7a6bd7917be468c5ef0a2860898766dd55497a563ddda94", size = 823938, upload-time = "2026-10-14T19:47:10.086Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c5/1aa13304c714ad81ab70feb6bd99f6514baafe8e6c84d243ffabae678379/pymongo-4.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:ac673404456b23c568cea326ab996a6b35a6009e41d42bcb774db025d0918b7d", size = 831074, upload-time = "2026-10-14T19:47:12.088Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a8/5de505ba380af3d10737a2d0ddd2c6752ff6e9a0fe484c992483efe74889/pymongo-4.19.0-cp314-cp314-win_arm64.whl", hash = "sha256:2bb0e7c422c14ff2b31ec8be3e6ecaad326c17fca17071bcfcd13482584a8e0f", size = 826686, upload-time = "2026-10-14T19:47:13.959Z" }, + { url = "https://files.pythonhosted.org/packages/9a/fc/eddcc314b76ab9f3ab1417ecc088f88336cc2bca5be1356c8aa3d183dda8/pymongo-4.19.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b01cc054878931ea81fc0a57c4c10489db723b8d7275fb10070f7228149012f1", size = 829839, upload-time = "2026-10-14T19:47:15.761Z" }, + { url = "https://files.pythonhosted.org/packages/87/51/caa4ac1f33d4b8a4de2469a0624ffc7f7fae7441f7d71d41c2be306734a4/pymongo-4.19.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:823f8b2fb59e4e635e296d5e92efa883e3d01a8faa477d515fc9dfe515368026", size = 830216, upload-time = "2026-10-14T19:47:17.789Z" }, + { url = "https://files.pythonhosted.org/packages/fc/e7/b3eb14aa900cfe7b6f7c0dd2349b5d0a488c17a9db76a8bfdf8bd30afd9d/pymongo-4.19.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1435721737b46be9bab5aa2374cfe57de934dc4ac421d5473308aa94c9fa39c3", size = 1113851, upload-time = "2026-10-14T19:47:19.743Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/ec2c2bdde80e23693703f01805a1e37509e088127177f2d5758ca05c9a79/pymongo-4.19.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dee18feff3203fa128798c6673c7795ef8a46d0b32c0e6b920c7b3f46129447", size = 1133130, upload-time = "2026-10-14T19:47:21.617Z" }, + { url = "https://files.pythonhosted.org/packages/40/df/4f1bada8fa02babd094a5c4ed8f4ea1dc76cfc1366b26238a2ad1fc55b51/pymongo-4.19.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8d866560dfbe44bc5e1110e96af4b8d92ffe6368c345dac1c36c8060188ebba6", size = 1152659, upload-time = "2026-10-14T19:47:23.572Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cb/a97d315c4c4e362d1f2e216d306122ae0f713ab457f73730684f3606a349/pymongo-4.19.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:47f04522f786dca82c776d5c3ed3ff9d08d6bf4cd0074c42296da5fac4d816ad", size = 1144177, upload-time = "2026-10-14T19:47:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/8d/59/2a6c68bdee03f326194361149c68ec6720a22460d11a2a43a0742a7d7fce/pymongo-4.19.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac55cf643eaa6146822f5f05f07be4dedbed906f525bb2ee098a865c4892788a", size = 1125673, upload-time = "2026-10-14T19:47:27.582Z" }, + { url = "https://files.pythonhosted.org/packages/20/c1/b108dda370e09db7a4dccfb2bb003e769a8dd98513135e4429040cb88b83/pymongo-4.19.0-cp314-cp314t-win32.whl", hash = "sha256:3bcebec2536a9aec1d490ad6fa9fc7ffc3329059fb1f99154efa5d594abdc98c", size = 826636, upload-time = "2026-10-14T19:47:29.463Z" }, + { url = "https://files.pythonhosted.org/packages/b9/55/a0da8479007f149838c094f6f863fc05c973abf6802654881a4dfc68858e/pymongo-4.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24668c6990bef96e1558328ba0802279cc1f752a3bcc7b283c2f39099a01e28c", size = 835337, upload-time = "2026-10-14T19:47:31.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/d0/9837244d18d8280277e7b2e9366ee2b9d35338052362888a4704d77ad633/pymongo-4.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:542b0f4e47fe68e753c85503f8352d4baa81ac73593601c8ede0fa22ba5c0431", size = 827513, upload-time = "2026-10-14T19:47:33.367Z" }, + { url = "https://files.pythonhosted.org/packages/97/6c/af80cf714a91b41441e9ad0aeac1af2000d902dfef7bac31388ba05bbfe7/pymongo-4.19.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:cc81d7ceeb7766254bce7ad7644dddb44241fb57555cd7c71de305b6903493b8", size = 826913, upload-time = "2026-10-14T19:47:35.317Z" }, + { url = "https://files.pythonhosted.org/packages/95/14/2ed9ee6c83fd05a36d310100562b599ea987d2339c57955b1afba80d07ec/pymongo-4.19.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:b602baef46ec5cd876fdf45dfdf864a58f5a507129393b93b8248249008f9a70", size = 827513, upload-time = "2026-10-14T19:47:37.463Z" }, + { url = "https://files.pythonhosted.org/packages/78/78/cd65885104e7b37f8cb7dd7e33d0b2c2415270afc2644ed643b52f526214/pymongo-4.19.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:179bc536b73fc76ae3d227114123ffc804f002fb45ddd996a81b233e806a0d2d", size = 1052003, upload-time = "2026-10-14T19:47:39.539Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ed/99fc74ed08dded2351818bf374303ddc400bd2e8b5ab297dac352aa0df56/pymongo-4.19.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4bd5e3ecd44d94b4eeef51f7e20a513206f2fceeab9534e9299c31133cc2e42", size = 1061762, upload-time = "2026-10-14T19:47:41.601Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8b/ded0ef32a2c4032cbec796f29b7b6067e76ac27714fbcfe06ce9a969b415/pymongo-4.19.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a38cfd2d81daef820a099c28065c6dc2ec9254ae80fefcf7981ea27e5381159", size = 1084314, upload-time = "2026-10-14T19:47:43.874Z" }, + { url = "https://files.pythonhosted.org/packages/52/64/82099393a7178c80fe1b16cc5dca94f388dec3df7a3f059a7b831bbf10dd/pymongo-4.19.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:567e509e1e01c956bfd5e60805b7d582aae45eeba34e9690d0da6f09560afb4f", size = 1075560, upload-time = "2026-10-14T19:47:45.904Z" }, + { url = "https://files.pythonhosted.org/packages/09/d2/1eab760f5dc3d09fbc8fec7ad2474def3c8d2efbeb8550bff12fed61f863/pymongo-4.19.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c3a47a6b325ac605352e9825ef658e6cca4f612e3a09838a564859f7d5435ea", size = 1060541, upload-time = "2026-10-14T19:47:47.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7d/426c1b661e8b4bd78671ea063ee66005faff0dfe6731ebdce0fb0000c339/pymongo-4.19.0-cp315-cp315-win32.whl", hash = "sha256:5d684e289cdb687f1508b15a44d3c0268f974c92ba129f658c1ef1fd196854e7", size = 823985, upload-time = "2026-10-14T19:47:50.253Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e9/f2ece0253d82d34fad0a316ffec848ac4e85357cae849cd5ea29def72ae4/pymongo-4.19.0-cp315-cp315-win_amd64.whl", hash = "sha256:546350d196b01b7feff7f8e6d140b6d4ab47486d5ae70dab858605cdfc2ffe1d", size = 831173, upload-time = "2026-10-14T19:47:52.418Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e0/be46ba1676cd04f831a9d4f6f8dbe0d3f816034788b8e3157762139f7aa8/pymongo-4.19.0-cp315-cp315-win_arm64.whl", hash = "sha256:d29ea47eebbeec81b67809fbb3440ffc53628d28f5b9f21624eed0038d9fddaa", size = 826603, upload-time = "2026-10-14T19:47:54.538Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/3e04d21eab4844372ef141d5cc4f5e03d4fb9ebda057dd5e5ef1db562433/pymongo-4.19.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:b7e8b5b546e31ac63255650b0bf764383885a6c657b3269e83b9e1e5de3ed129", size = 829820, upload-time = "2026-10-14T19:47:56.428Z" }, + { url = "https://files.pythonhosted.org/packages/44/9c/dbad3291c3614a884285d10e2cc123567386d682bf8a08caf5e0a630bf3e/pymongo-4.19.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:f21109534f5555cf77689ad323a21fbc07e8a397b34f157938a347725d83b7b5", size = 830313, upload-time = "2026-10-14T19:47:58.457Z" }, + { url = "https://files.pythonhosted.org/packages/68/2d/17e783859c89e749fe63803a08ab5e85ca0ee8416f0cbe84d5fe6efa2981/pymongo-4.19.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3af5ab5a9e490580d3f40660665f0f4d579a324e25acee6372e1508e4b7c7b7a", size = 1117096, upload-time = "2026-10-14T19:48:00.917Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/0b23e363eb5856ecfdf3b7edbdfea7f964da664eb78e507bb8375820c7e5/pymongo-4.19.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb9d9bff4f666405cd9d7a17b6127294394847dce60ca38d8ba45f4879ada6c9", size = 1134854, upload-time = "2026-10-14T19:48:03.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/b8/60758f35a90729d77fdfd36eeff5ddf191d9f198528074884d816865d942/pymongo-4.19.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be75840640e98ea4b5f150bceda8a55f1085e395732e21da028195da30ae79b5", size = 1154121, upload-time = "2026-10-14T19:48:05.638Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b0/e2b56cf154bf1dff7deca641de160215f9163253609a8beb780dc35f007b/pymongo-4.19.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa39c6ddaf987a48ef073ff7fc225b84282079a46fbabaea9c5fcb6f89476e44", size = 1143786, upload-time = "2026-10-14T19:48:07.734Z" }, + { url = "https://files.pythonhosted.org/packages/d1/88/39b61ede07785568d47229a01e7e82fc3903f5cac55ad377e0e64a0d324a/pymongo-4.19.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b92aa4cc4b0bf67a18e3c73062ef70e00ca6921c742aa4d0f4770a493193c661", size = 1126770, upload-time = "2026-10-14T19:48:09.891Z" }, + { url = "https://files.pythonhosted.org/packages/40/f2/391d41d24384545b2a6ed09694b2444f765a6e20932c75ed4eb507c9ef36/pymongo-4.19.0-cp315-cp315t-win32.whl", hash = "sha256:eececca812e8f5b3c12ad33dc90201ac20f5f193da446f7719f4321a0841387b", size = 826638, upload-time = "2026-10-14T19:48:11.962Z" }, + { url = "https://files.pythonhosted.org/packages/d1/48/96b923a2d29456896c7f11f8e6104339818112f5a8621f42ba51f131a510/pymongo-4.19.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f17b100fdc16b65c12997ec4fcc78eecc0a6395254c7ec92a4596e855ff1f33a", size = 835209, upload-time = "2026-10-14T19:48:14.063Z" }, + { url = "https://files.pythonhosted.org/packages/46/6b/2ede9f64d96393e8111d250620f5340d64e62f4617322a43800516027ce9/pymongo-4.19.0-cp315-cp315t-win_arm64.whl", hash = "sha256:bfcb5f8912edd9714a52564ad41c0dcd72e5408d1d3d67b41f6145df4a516318", size = 827452, upload-time = "2026-10-14T19:48:17.534Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/26/2fbeedb218a787a5eea551c7532cac4e009f83d689dd2faa0d0353473f86/python_dotenv-1.2.4.tar.gz", hash = "sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0", size = 60824, upload-time = "2026-10-01T05:36:10Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/d1/38f3a3405989a89ac18390803e70c6ad7c7760da4f9b83cbeca0c44a0c72/python_dotenv-1.2.4-py3-none-any.whl", hash = "sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc", size = 23266, upload-time = "2026-10-01T05:36:08.633Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "slrmentor-backend" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "flask" }, + { name = "flask-cors" }, + { name = "flask-pymongo" }, + { name = "google-genai" }, + { name = "gunicorn" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "pymongo" }, + { name = "python-dotenv" }, + { name = "requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "flask", specifier = ">=3.1.3" }, + { name = "flask-cors", specifier = ">=6.0.5" }, + { name = "flask-pymongo", specifier = ">=2.3.0" }, + { name = "google-genai", specifier = ">=2.10.0" }, + { name = "gunicorn", specifier = ">=26.1.0" }, + { name = "openai", specifier = ">=2.44.0" }, + { name = "pydantic", specifier = ">=2.13.4" }, + { name = "pymongo", extras = ["srv"], specifier = ">=4.6.1" }, + { name = "python-dotenv", specifier = ">=1.2.4" }, + { name = "requests", specifier = ">=2.34.2" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "truststore" +version = "0.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9f/c5201d42a484c061e528825fc8e2d565f5abd50a4ced6fb7d29c4ec99b2b/truststore-0.10.5.tar.gz", hash = "sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd", size = 28091, upload-time = "2026-10-12T22:27:31.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e9/3a7820be2bb0fe53b6bc9c3be26d3d1158004e4c3ab953aa6840b955b1e9/truststore-0.10.5-py3-none-any.whl", hash = "sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c", size = 19017, upload-time = "2026-10-12T22:27:30.377Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] + +[[package]] +name = "websockets" +version = "16.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/f7/bc3a25c5ec26ce62ce487690becc2f3710bbc7b33338f005ad390db0b986/websockets-16.1.1.tar.gz", hash = "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57", size = 182204, upload-time = "2026-07-17T22:51:05.858Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/a2/ba78a164eeea4620df4a4df4bd2ed6017438c4655cc0f36f2c0bc0432355/websockets-16.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:443aefe96b7fdb132e2a70806cca1f2af49bb3f28e47abcd7c2e9dcf4d8fa1b8", size = 179635, upload-time = "2026-07-17T22:50:05.001Z" }, + { url = "https://files.pythonhosted.org/packages/b9/08/d26d7a7628cd4ac34cbbdb63ac80914ca842ed8e42938c40a53567806df3/websockets-16.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6456ff333092d509127d75a638cb411afae8ff17f092635015d1902efec8a293", size = 177320, upload-time = "2026-07-17T22:50:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/0f/45/ebec83e6269536aa5932533c67b0af5c781f3e73fdbcd68672dcf43f4f44/websockets-16.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fce6c48559c86d1ac3632ccb1bebc7d5442fbe79bd9bb0e40379ee54be2a4051", size = 177544, upload-time = "2026-07-17T22:50:07.834Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d5/abc614d2297f6c1c3e01e61260364457a47c25cc1cf6a879038902bc6aa8/websockets-16.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:92b820d345f7a3fc7b8163949ee92df910f290c3fc517b3d5301c78065adafe1", size = 187270, upload-time = "2026-07-17T22:50:09.275Z" }, + { url = "https://files.pythonhosted.org/packages/52/71/4c99af3b87dff1b2927981f6876607d4acb45338c665242168d3982f7758/websockets-16.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a606d9c24035242a3e256e9d5b77ed9cd6bccfcb7cf993e5ca3c0f6f68fb6a7", size = 188509, upload-time = "2026-07-17T22:50:10.722Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5c8ca14b0df7eb84ed0524165c5359150210140817a3312aee57bf62a1cf/websockets-16.1.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:414e596c75f74e0994084694189d7dc9229fb278e33064d6784b73ffbba3ca31", size = 189882, upload-time = "2026-07-17T22:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/25/c1/bedfba9e70557129cb8083748d167bdcc01483dedf0f0df143676df05cbe/websockets-16.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:536676848fc5961aca9d20389951f59169508f765637a172403dc5434d722fa0", size = 189114, upload-time = "2026-07-17T22:50:13.789Z" }, + { url = "https://files.pythonhosted.org/packages/df/09/aa835b2787835aebd839114be5de51b797cb480b63ba42b26d34dfe147cb/websockets-16.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97fd3a0e8b53efa41970ac1dff3d8cf0d2884cadeb4caaf95db7ad1526926ee3", size = 187861, upload-time = "2026-07-17T22:50:15.179Z" }, + { url = "https://files.pythonhosted.org/packages/20/26/f6408330694dbc9830857d9d23bc14ac4f6875127a480cfdda8d5ca21198/websockets-16.1.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7b1b19636af86a3c7995d4d028dbe376f39b4bf31541146f9c123582a6c94562", size = 185286, upload-time = "2026-07-17T22:50:16.741Z" }, + { url = "https://files.pythonhosted.org/packages/17/9a/e0675e70dd8a80762cf35bb18799d3f290a4890ffe6439bc51d222796083/websockets-16.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41c8e77f17294c0ac18008a7309b99b34ee72247ef10b6dff4c3f8b5ac29896b", size = 187935, upload-time = "2026-07-17T22:50:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/33/c1/3234cfb86afde01b81e9bddcc6e534c440975d60a13991259e833069ab3e/websockets-16.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9f63bcef7f4b02b06b35fc01c93b96c43b5e88e1e8868676caacf493d5a31f3a", size = 186444, upload-time = "2026-07-17T22:50:19.67Z" }, + { url = "https://files.pythonhosted.org/packages/89/87/9c15206e1d778923d8daa9657de07aa62ea815e13448319c98458c37b281/websockets-16.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dab9eb87869da2d6ed3af3f3adf28414baae6ec9d4df355ffc18889132f3436c", size = 188409, upload-time = "2026-07-17T22:50:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/cf5de5c67676de2d3eef8b2a518f168f6796595447a5b7161ba0d012915c/websockets-16.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:43e3a9fdd7cbf7ba6040c31fae0faf84ca1474fef777c4e37912f1540f854499", size = 185958, upload-time = "2026-07-17T22:50:22.719Z" }, + { url = "https://files.pythonhosted.org/packages/62/c0/731b6ddede2e4136912ec4cff2cffbda35af73546be4762c3d7bd3bd79af/websockets-16.1.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:056ae37939ed7e9974f364f5864e76e49182622d8f9751ac1903c0d09b013985", size = 186911, upload-time = "2026-07-17T22:50:24.108Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7f/39c634472c4469a24a7c09cecddffb08fac6d0e74f73881a94ee8a40a196/websockets-16.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0eadbbf2c30f01efa58e1f110eb6fa293261f6b0b1aa38f7f48707107690af9", size = 187204, upload-time = "2026-07-17T22:50:25.548Z" }, + { url = "https://files.pythonhosted.org/packages/26/89/9667c256c256dafcc62d21328ce7a40067da857969b68ee9af375b0aaf72/websockets-16.1.1-cp314-cp314-win32.whl", hash = "sha256:195c978b065fa40910582464f99d6b15c8b314c68e0546549a55ed83f4735328", size = 179603, upload-time = "2026-07-17T22:50:27.086Z" }, + { url = "https://files.pythonhosted.org/packages/bd/dd/1c099d6c0fc5deb6b46ccdbb6981fdb4b12c917869cb3952408409dc18db/websockets-16.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:4e8d01cc3bcae7bbf8167f944aeafefed590fae5693552bba9794a9df68371cc", size = 179948, upload-time = "2026-07-17T22:50:28.521Z" }, + { url = "https://files.pythonhosted.org/packages/35/25/9956b2d5e0529d5d23924f21bba1440d4c5c88a562e4f08550871ffa97a7/websockets-16.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0ffd3031ea8bda8d61762e84220186105ba3b748b3c8da2ae4f7816fac03e573", size = 179963, upload-time = "2026-07-17T22:50:29.982Z" }, + { url = "https://files.pythonhosted.org/packages/17/06/55ffc976c488b6aee9ea05761ff7c4e88e7c1fd82818c8ca7b556ad2f90c/websockets-16.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84a2cef8deffbd9ab8ee0ea546a2a6a7030c28f44e6cdd4547dbfeb489eb8999", size = 177497, upload-time = "2026-07-17T22:50:31.396Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/f7dac2e980bacc92bdc26cebae4ae4d50cae5380732c50980598fc0bbae4/websockets-16.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3df13f73af9b3b38ab1195eb299ecb67a4330c911c97ae04043ff74085728abe", size = 177698, upload-time = "2026-07-17T22:50:32.829Z" }, + { url = "https://files.pythonhosted.org/packages/b2/39/26762f734113e22da2b942c3aca85798e0c0405d64c256549540ff31e5a1/websockets-16.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:23253dd5bcae3f9aaee0a1d30967a8dbd52e5d3cff93a2e5b84df57b77d4750d", size = 187561, upload-time = "2026-07-17T22:50:34.24Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/c3f330851806b9b02138b774d593478323e73c99238681b4b93efe64e02d/websockets-16.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1c5705e314449e3308872fe084b8571ce078ee4fc55a98a769bdefe5917392", size = 188732, upload-time = "2026-07-17T22:50:36.088Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f2/eb2c450f052de334ae33cf200ece6e87b0e14d186807074e4eb1cd2cdea2/websockets-16.1.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69e52d175a0a7d1e13b4b67ad41c560b7d98e8c6f6126eb0bda496c784faf8c7", size = 190872, upload-time = "2026-07-17T22:50:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/70/31/2ac8cecf3a74f7fed9132129fc3d90b3998a1554570c11a69b2a8c20332d/websockets-16.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f79c89b5eb034d1722938a891916582f8f7f503f58ca22518a63c3f2cd18499", size = 189305, upload-time = "2026-07-17T22:50:39.53Z" }, + { url = "https://files.pythonhosted.org/packages/6a/cf/8ab19650d3c0d4562c92e70ab47c257c4aa5c6a713ed87fe63766b31fefc/websockets-16.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39f2a024af5c345ffe8fcf1ee18c049c024c94df393bb09b044a6917c77bde43", size = 188033, upload-time = "2026-07-17T22:50:40.912Z" }, + { url = "https://files.pythonhosted.org/packages/66/d7/a49a38a6127a4acb134fb1912b215d900cc657605cff32445bf519f3acc4/websockets-16.1.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:952303a7318d4cbe1011400839bb2051c9f84fa0a35923267f5daba34b15d458", size = 185748, upload-time = "2026-07-17T22:50:42.559Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/ad1fa40388c7f2e0bb2c7930d0090b6c5498594bd1cdaec18864df3d9e97/websockets-16.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:249116b4a76063d930a46391ad56e135c286e4562a18309029fc2c73f4ed4c62", size = 188285, upload-time = "2026-07-17T22:50:43.974Z" }, + { url = "https://files.pythonhosted.org/packages/35/b8/d5db28ca264b9104f82196f92dc8843e35fd391f763d42e4ad358f5bc97e/websockets-16.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:61922544a0587a13fd3f53e4c0e5e606510c7b0d9d22c8444e5fae22a06b38cb", size = 186777, upload-time = "2026-07-17T22:50:45.474Z" }, + { url = "https://files.pythonhosted.org/packages/42/9c/726cb39d0cc43ae848dce4aa2acb04eecc6738b1264ec6d700bf6bcfb9f8/websockets-16.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:46dcaa042cd1de6c59e7d9269fa63ff7572b6df40510600b678f0826b3c7af51", size = 188682, upload-time = "2026-07-17T22:50:46.973Z" }, + { url = "https://files.pythonhosted.org/packages/be/c7/1168704de8c2dd483edabe4a22cbe4465dd8be8dd95561d214f9fe092871/websockets-16.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:38565aca3e01ea8734e578fb2118dade0ecb0250533f29e22b8d1a7a196cf4d0", size = 186377, upload-time = "2026-07-17T22:50:48.413Z" }, + { url = "https://files.pythonhosted.org/packages/ca/40/f9ff2d630ffce4e7dfea0b2288e1caf9ebbf9ff8a9ec9396136ce8b94935/websockets-16.1.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:42f599f4d48c7e1a3338fdaac3acd075be3b3cf02d4b274f3bf2767aedd3d217", size = 187148, upload-time = "2026-07-17T22:50:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/e177c8299f78d7cbe2d14df228643c10c70c0e86e108e092056bbcc16e46/websockets-16.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcc04fedf83effaeb9cce98abc9469bb1b42ef85f03e01c8c1f4438ef7555737", size = 187578, upload-time = "2026-07-17T22:50:51.619Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/b6987faf330f5af5c787a2610124c2e8403d51724f9001ec4fff6311fe7a/websockets-16.1.1-cp314-cp314t-win32.whl", hash = "sha256:8483c2096363120eea8b07c06ae7304d520f686665fffd4811fad423930a65d7", size = 179729, upload-time = "2026-07-17T22:50:53.269Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6e/fbac6ed878dd362fbad7d415fa4f84d38e3e33fed8cde45c64e783acf826/websockets-16.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bcce07e23e5769375158f5efdcdafa8d5cd014b93c6683865b840ed65b96f231", size = 180072, upload-time = "2026-07-17T22:50:54.969Z" }, + { url = "https://files.pythonhosted.org/packages/be/4d/2d0d67834092e354d2b0498f014a41249a89556bc406cf86f3e1557bb463/websockets-16.1.1-py3-none-any.whl", hash = "sha256:6abbd3e82c731c8e531714466acd5d87b5e88ac3243465337ba71d68e23ae7e3", size = 173814, upload-time = "2026-07-17T22:51:04.184Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/34/4dd12fc8bb7d61c91467ec3efe415ffa7d5456f799954b40c5bbaeae470e/werkzeug-3.1.9.tar.gz", hash = "sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060", size = 940188, upload-time = "2026-09-27T18:33:41.637Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/38/df03f564f43cec2684823f3cccae1a652ee7face1cbaa76fb223096e64d7/werkzeug-3.1.9-py3-none-any.whl", hash = "sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab", size = 228700, upload-time = "2026-09-27T18:33:39.685Z" }, +]